diff --git a/Cargo.lock b/Cargo.lock index 014a784..7171ed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2407,6 +2407,7 @@ dependencies = [ "glob", "indexmap", "itertools 0.13.0", + "lazy_static", "rpassword", "rustyline 14.0.0", "semver 1.0.23", @@ -4014,11 +4015,11 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -5227,7 +5228,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -5947,12 +5948,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" diff --git a/Cargo.toml b/Cargo.toml index 1b1fe2b..a622f29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,4 @@ futures-util = "0.3.30" semver = "1.0.23" shellexpand = { version = "3.1.0", features = ["path"] } indexmap = "2.2.6" +lazy_static = "1.5.0" diff --git a/src/interpreter/block_functions.rs b/src/interpreter/block_functions.rs deleted file mode 100644 index 1ebeeda..0000000 --- a/src/interpreter/block_functions.rs +++ /dev/null @@ -1,61 +0,0 @@ -use alloy::{eips::BlockId, rpc::types::BlockTransactionsKind}; -use anyhow::{anyhow, bail, Result}; - -use super::{Env, Value}; - -#[derive(Debug, PartialEq, Clone, Hash, Eq)] -pub enum BlockFunction { - ChainId, - BaseFee, - Number, - Timestamp, -} - -impl std::fmt::Display for BlockFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BlockFunction::ChainId => write!(f, "chainid"), - BlockFunction::BaseFee => write!(f, "basefee"), - BlockFunction::Number => write!(f, "number"), - BlockFunction::Timestamp => write!(f, "timestamp"), - } - } -} - -impl BlockFunction { - pub fn from_name(s: &str) -> Result { - match s { - "chainid" => Ok(BlockFunction::ChainId), - "basefee" => Ok(BlockFunction::BaseFee), - "number" => Ok(BlockFunction::Number), - "timestamp" => Ok(BlockFunction::Timestamp), - _ => bail!("unknown block function: {}", s), - } - } - - pub fn all() -> Vec { - ["chainid", "basefee", "number", "timestamp"] - .iter() - .map(|s| s.to_string()) - .collect() - } - - pub async fn execute(&self, args: &[Value], env: &mut Env) -> Result { - if !args.is_empty() { - bail!("block.{} does not take arguments", self); - } - match self { - BlockFunction::ChainId => Ok(env.get_provider().get_chain_id().await?.into()), - BlockFunction::BaseFee => Ok(env.get_provider().get_gas_price().await?.into()), - BlockFunction::Number => Ok(env.get_provider().get_block_number().await?.into()), - BlockFunction::Timestamp => { - let latest_block = env - .get_provider() - .get_block(BlockId::latest(), BlockTransactionsKind::Hashes) - .await? - .ok_or(anyhow!("latest block not found"))?; - Ok(latest_block.header.timestamp.into()) - } - } - } -} diff --git a/src/interpreter/builtin_functions.rs b/src/interpreter/builtin_functions.rs deleted file mode 100644 index 7f93e11..0000000 --- a/src/interpreter/builtin_functions.rs +++ /dev/null @@ -1,477 +0,0 @@ -use core::fmt; - -use alloy::{ - dyn_abi::{DynSolType, DynSolValue, JsonAbiExt}, - hex, - json_abi::JsonAbi, - primitives::{Address, FixedBytes, B256, I256, U256}, - providers::PendingTransactionBuilder, -}; -use anyhow::{bail, Result}; -use futures::{future::BoxFuture, FutureExt}; -use itertools::Itertools; - -use super::{ - block_functions::BlockFunction, - types::{ContractInfo, HashableIndexMap, Receipt, Type}, - Directive, Env, Value, -}; - -fn common_to_decimals( - value: T, - decimals: Option, - precision: Option, - to_f64: F, - pow: G, -) -> Result -where - T: Copy + std::ops::Div, - F: Fn(T) -> Result, - G: Fn(u32) -> T, -{ - let decimals = decimals.unwrap_or(18); - let precision = precision.unwrap_or(2); - let result = if decimals > precision { - let downscaled = value / pow((decimals - precision - 1) as u32); - match to_f64(downscaled) { - Ok(res) => Ok(res / 10f64.powi(precision + 1)), - _ => to_f64(value / pow(decimals as u32)), - } - } else { - to_f64(value / pow(decimals as u32)) - }; - result.map(|result| format!("{:.prec$}", result, prec = precision as usize)) -} - -fn uint_to_decimals(value: U256, decimals: Option, precision: Option) -> Result { - common_to_decimals( - value, - decimals, - precision, - |v: U256| Ok(TryInto::::try_into(v).map(|v| v as f64)?), - |exp| U256::from(10u64).pow(U256::from(exp)), - ) -} - -fn int_to_decimals(value: I256, decimals: Option, precision: Option) -> Result { - common_to_decimals( - value, - decimals, - precision, - |v: I256| Ok(TryInto::::try_into(v).map(|v| v as f64)?), - |exp| I256::from_raw(U256::from(10u64).pow(U256::from(exp))), - ) -} - -fn to_decimals(value: T, args: &[Value], func: F) -> Result -where - F: Fn(T, Option, Option) -> Result, -{ - let decimals = args.first().map(|v| v.as_i32()).transpose()?; - let precision = args.get(1).map(|v| v.as_i32()).transpose()?; - func(value, decimals, precision) -} - -fn concat_strings(string: String, args: &[Value]) -> Result { - if let Some(Value::Str(s)) = args.first() { - Ok(format!("{}{}", string, s)) - } else { - bail!("cannot concat {} with {:?}", string, args) - } -} - -fn concat_arrays(arr: Vec, args: &[Value]) -> Result> { - if let Some(Value::Array(other)) = args.first() { - let mut new_arr = arr.clone(); - new_arr.extend(other.clone()); - Ok(new_arr) - } else { - bail!("cannot concat {:?} with {:?}", arr, args) - } -} - -fn concat_bytes(bytes: Vec, args: &[Value]) -> Result> { - if let Some(Value::Bytes(other)) = args.first() { - let mut new_bytes = bytes.clone(); - new_bytes.extend(other.clone()); - Ok(new_bytes) - } else { - bail!("cannot concat {:?} with {:?}", bytes, args) - } -} - -fn concat(value: &Value, args: &[Value]) -> Result { - match value { - Value::Str(s) => concat_strings(s.clone(), args).map(Value::Str), - Value::Array(arr) => concat_arrays(arr.clone(), args).map(Value::Array), - Value::Bytes(b) => concat_bytes(b.clone(), args).map(Value::Bytes), - _ => bail!("cannot concat {}", value), - } -} - -fn get_type(args: &[Value]) -> Result { - if let [arg] = args { - Ok(arg.get_type()) - } else { - bail!("type function expects one argument") - } -} - -fn decode_calldata(name: &str, abi: &JsonAbi, args: &[Value]) -> Result { - let data = match args.first() { - Some(Value::Bytes(data)) => data, - _ => bail!("decode function expects bytes as an argument"), - }; - let selector = FixedBytes::<4>::from_slice(&data[..4]); - let function = abi - .functions() - .find(|f| f.selector() == selector) - .ok_or(anyhow::anyhow!( - "function with selector {} not found for {}", - selector, - name - ))?; - let decoded = function.abi_decode_input(&data[4..], true)?; - let values = decoded - .into_iter() - .map(Value::try_from) - .collect::>>()?; - Ok(Value::Tuple(values)) -} - -fn map<'a>( - target: &'a [Value], - ty: Type, - args: &'a [Value], - env: &'a mut Env, -) -> BoxFuture<'a, Result> { - async move { - let func_value = args - .first() - .ok_or_else(|| anyhow::anyhow!("map function expects a single argument"))?; - let mut values = vec![]; - for v in target { - let value = match func_value { - Value::Func(func) => func.execute(&[v.clone()], env).await?, - Value::TypeObject(type_) => type_.cast(v)?, - _ => bail!("map function expects a function or type as an argument"), - }; - values.push(value); - } - match ty { - Type::Tuple(_) => Ok(Value::Tuple(values)), - Type::Array(_) => Ok(Value::Array(values)), - _ => bail!("cannot map to type {}", ty), - } - } - .boxed() -} - -fn format_bytes(bytes: &[u8]) -> String { - let mut stripped_bytes = bytes; - let last_0 = bytes.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1); - if last_0 > 0 { - stripped_bytes = &bytes[..last_0]; - } - let is_diplayable = bytes.iter().all(|c| c.is_ascii()); - if is_diplayable { - return String::from_utf8_lossy(stripped_bytes).to_string(); - } else { - format!("0x{}", hex::encode(bytes)) - } -} - -fn format(value: &Value, args: &[Value]) -> Result { - match value { - Value::Uint(n, _) => to_decimals(*n, args, uint_to_decimals), - Value::Int(n, _) => to_decimals(*n, args, int_to_decimals), - Value::Str(s) => Ok(s.clone()), - Value::Bytes(b) => Ok(format_bytes(b)), - Value::FixBytes(b, _) => Ok(format_bytes(&b.0)), - v => Ok(format!("{}", v)), - } -} - -fn format_func(args: &[Value]) -> Result { - let receiver = args - .first() - .ok_or_else(|| anyhow::anyhow!("format function expects at least one argument"))?; - format(receiver, &args[1..]) -} - -fn mul_div_args(args: &[Value]) -> Result<(Value, u64)> { - match args { - [v2] => Ok((v2.clone(), 18)), - [v2, d] => Ok((v2.clone(), d.as_u64()?)), - _ => bail!("mul function expects one or two arguments"), - } -} - -fn abi_encode(args: &[Value]) -> Result { - let arr = Value::Tuple(args.to_vec()); - let dyn_sol = DynSolValue::try_from(&arr)?; - let abi_encoded = dyn_sol.abi_encode(); - Ok(Value::Bytes(abi_encoded)) -} - -fn abi_decode(args: &[Value]) -> Result { - let (data, sol_type) = match args { - [Value::Bytes(data_), type_ @ Value::Tuple(_)] => { - (data_, DynSolType::try_from(type_.get_type())?) - } - [Value::Bytes(data_), Value::TypeObject(ty)] => { - (data_, DynSolType::Tuple(vec![ty.clone().try_into()?])) - } - _ => bail!("abi.decode function expects bytes and tuple of types as argument"), - }; - let decoded = sol_type.abi_decode(data)?; - decoded.try_into() -} - -fn abi_encode_packed(args: &[Value]) -> Result { - let arr = Value::Tuple(args.to_vec()); - let dyn_sol = DynSolValue::try_from(&arr)?; - let abi_encoded = dyn_sol.abi_encode_packed(); - Ok(Value::Bytes(abi_encoded)) -} - -async fn wait_for_receipt(tx: B256, env: &mut Env, args: &[Value]) -> Result { - let provider = env.get_provider(); - let tx = PendingTransactionBuilder::new(provider.root(), tx); - if args.len() > 1 { - bail!("get_receipt function expects at most one argument") - } - let timeout = args.first().map_or(Ok(30), |v| v.as_u64())?; - let receipt = tx - .with_required_confirmations(1) - .with_timeout(Some(std::time::Duration::from_secs(timeout))) - .get_receipt() - .await?; - Ok(Value::TransactionReceipt(receipt.into())) -} - -fn keccak256(args: &[Value]) -> Result { - let data = match args.first() { - Some(Value::Bytes(data)) => data, - _ => bail!("keccak256 function expects bytes as an argument"), - }; - Ok(Value::FixBytes(alloy::primitives::keccak256(data), 32)) -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum BuiltinFunction { - Balance(Address), - FormatFunc, - Format(Box), - Mul(Box), - Div(Box), - Min(Type), - Max(Type), - Concat(Box), - Length(Box), - Decode(String, JsonAbi), - Map(Vec, Type), - Keys(HashableIndexMap), - Directive(Directive), - GetType, - Log, - Block(BlockFunction), - GetReceipt(B256), - ReadReceipt(Receipt, String), - AbiEncode, - AbiEncodePacked, - AbiDecode, - Keccak256, -} - -impl fmt::Display for BuiltinFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Balance(addr) => write!(f, "{}.balance", addr), - Self::Format(v) => write!(f, "{}.format", v), - Self::Concat(s) => write!(f, "{}.concat", s), - Self::Length(s) => write!(f, "{}.length", s), - Self::Mul(v) => write!(f, "{}.mul", v), - Self::Div(v) => write!(f, "{}.div", v), - Self::Min(t) => write!(f, "{}.min", t), - Self::Max(t) => write!(f, "{}.max", t), - Self::Decode(name, _) => write!(f, "{}.decode(bytes)", name), - Self::Map(v, _) => { - let items = v.iter().map(|v| format!("{}", v)).join(", "); - write!(f, "{}.map", items) - } - Self::Keys(v) => { - let items = v.0.iter().map(|(k, v)| format!("{}: {}", k, v)).join(", "); - write!(f, "{{{}}}.keys", items) - } - Self::GetType => write!(f, "type"), - Self::FormatFunc => write!(f, "format"), - Self::Directive(d) => write!(f, "repl.{}", d), - Self::Block(func) => write!(f, "block.{}", func), - Self::GetReceipt(tx) => write!(f, "{}.get_receipt", Value::FixBytes(*tx, 32)), - Self::ReadReceipt(receipt, name) => write!(f, "{}.{}", receipt, name), - Self::Log => write!(f, "console.log"), - Self::AbiEncode => write!(f, "abi.encode"), - Self::AbiEncodePacked => write!(f, "abi.encodePacked"), - Self::AbiDecode => write!(f, "abi.decode"), - Self::Keccak256 => write!(f, "keccak256"), - } - } -} - -impl BuiltinFunction { - pub fn from_name(name: &str) -> Result { - match name { - "format" => Ok(Self::FormatFunc), - "type" => Ok(Self::GetType), - "keccak256" => Ok(Self::Keccak256), - _ => bail!("no function {}", name), - } - } - - pub fn functions() -> Vec { - let functions = ["format", "type", "keccak256"]; - functions.iter().map(|s| s.to_string()).collect() - } - - pub fn with_receiver(receiver: &Value, name: &str) -> Result { - let method = match (receiver, name) { - (Value::Addr(addr), "balance") => Self::Balance(*addr), - - (v, "format") => Self::Format(Box::new(v.clone())), - - (v @ (Value::Str(_) | Value::Array(_) | Value::Bytes(_)), "concat") => { - Self::Concat(Box::new(v.clone())) - } - - (v @ Value::Uint(..) | v @ Value::Int(..), "mul") => Self::Mul(Box::new(v.clone())), - (v @ Value::Uint(..) | v @ Value::Int(..), "div") => Self::Div(Box::new(v.clone())), - - (Value::Tuple(values), "map") => Self::Map( - values.clone(), - Type::Tuple(values.iter().map(Value::get_type).collect()), - ), - - (Value::Array(values), "map") => { - let arr_type = values.first().map_or(Type::Uint(256), Value::get_type); - Self::Map(values.clone(), Type::Array(Box::new(arr_type))) - } - ( - v @ (Value::Array(_) | Value::Bytes(_) | Value::Str(_) | Value::Tuple(_)), - "length", - ) => Self::Length(Box::new(v.clone())), - - (Value::TypeObject(Type::Type(t)), "max") if t.is_int() => Self::Max(*t.clone()), - (Value::TypeObject(Type::Type(t)), "min") if t.is_int() => Self::Min(*t.clone()), - - (Value::Mapping(values, _, _), "keys") => Self::Keys(values.clone()), - - (Value::Transaction(tx), "getReceipt") => Self::GetReceipt(*tx), - - (Value::TransactionReceipt(r), name) => Self::ReadReceipt(r.clone(), name.to_string()), - - (Value::TypeObject(Type::Contract(ContractInfo(name, abi))), "decode") => { - Self::Decode(name.clone(), abi.clone()) - } - (Value::TypeObject(Type::Repl), _) => { - Directive::from_name(name).map(Self::Directive)? - } - (Value::TypeObject(Type::Block), _) => { - BlockFunction::from_name(name).map(Self::Block)? - } - (Value::TypeObject(Type::Console), "log") => Self::Log, - - (Value::TypeObject(Type::Abi), "encode") => Self::AbiEncode, - (Value::TypeObject(Type::Abi), "encodePacked") => Self::AbiEncodePacked, - (Value::TypeObject(Type::Abi), "decode") => Self::AbiDecode, - - _ => bail!("no method {} for type {}", name, receiver.get_type()), - }; - Ok(method) - } - - pub fn is_property(&self) -> bool { - match self { - Self::Balance(_) - | Self::Block(_) - | Self::Min(_) - | Self::Max(_) - | Self::Length(_) - | Self::ReadReceipt(_, _) - | Self::Keys(_) => true, - Self::Directive(d) => d.is_property(), - _ => false, - } - } - - pub async fn execute(&self, args: &[Value], env: &mut Env) -> Result { - match self { - Self::Balance(addr) => Ok(Value::Uint( - env.get_provider().get_balance(*addr).await?, - 256, - )), - Self::FormatFunc => format_func(args).map(Value::Str), - Self::Format(v) => format(v, args).map(Value::Str), - Self::Concat(v) => concat(v, args), - - Self::Mul(v) => { - let (value, decimals) = mul_div_args(args)?; - (v.as_ref().clone() * value.clone())? / Value::decimal_multiplier(decimals as u8) - } - Self::Div(v) => { - let (value, decimals) = mul_div_args(args)?; - (v.as_ref().clone() * Value::decimal_multiplier(decimals as u8))? / value.clone() - } - - Self::Max(t) => t.max(), - Self::Min(t) => t.min(), - - Self::Length(v) => v.len().map(|v| Value::Uint(U256::from(v), 256)), - - Self::Keys(values) => Ok(Value::Array(values.0.keys().cloned().collect_vec())), - - Self::Keccak256 => keccak256(args), - - Self::AbiEncode => abi_encode(args), - Self::AbiEncodePacked => abi_encode_packed(args), - Self::AbiDecode => abi_decode(args), - - Self::Decode(name, abi) => decode_calldata(name, abi, args), - Self::Map(values, type_) => { - let result = map(values, type_.clone(), args, env).await?; - Ok(result) - } - Self::GetType => get_type(args).map(Value::TypeObject), - Self::Directive(d) => d.execute(args, env).await, - Self::Block(f) => f.execute(args, env).await, - Self::GetReceipt(tx) => wait_for_receipt(*tx, env, args).await, - Self::ReadReceipt(receipt, name) => receipt.get(name), - Self::Log => { - args.iter().for_each(|arg| println!("{}", arg)); - Ok(Value::Null) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_uint_to_decimals() -> Result<()> { - let value = U256::from(10).pow(U256::from(18)); - assert_eq!(uint_to_decimals(value, None, None)?, "1.00"); - - assert_eq!( - uint_to_decimals(U256::from(12348000), Some(6), None)?, - "12.35" - ); - assert_eq!( - uint_to_decimals(U256::from(12348000), Some(6), Some(3))?, - "12.348" - ); - - Ok(()) - } -} diff --git a/src/interpreter/builtins/abi.rs b/src/interpreter/builtins/abi.rs new file mode 100644 index 0000000..c8d83d3 --- /dev/null +++ b/src/interpreter/builtins/abi.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use crate::interpreter::{ + functions::{FunctionDef, FunctionParam, SyncMethod}, + ContractInfo, Env, Type, Value, +}; +use alloy::dyn_abi::{DynSolType, DynSolValue, JsonAbiExt}; +use anyhow::{bail, Result}; +use lazy_static::lazy_static; + +fn abi_decode(_env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let (data, sol_type) = match args { + [Value::Bytes(data_), Value::Tuple(values)] => { + let types = values + .iter() + .map(|v| match v { + Value::TypeObject(ty) => ty.clone().try_into(), + _ => bail!("abi.decode function expects tuple of types as argument"), + }) + .collect::>>()?; + (data_, DynSolType::Tuple(types)) + } + [Value::Bytes(data_), Value::TypeObject(ty)] => { + (data_, DynSolType::Tuple(vec![ty.clone().try_into()?])) + } + _ => bail!("abi.decode function expects bytes and tuple of types as argument"), + }; + let decoded = sol_type.abi_decode(data)?; + decoded.try_into() +} + +fn abi_decode_calldata(_env: &mut Env, receiver: &Value, args: &[Value]) -> Result { + let (name, abi) = match receiver { + Value::TypeObject(Type::Contract(ContractInfo(name, abi))) => (name, abi), + _ => bail!("decode function expects contract type as first argument"), + }; + let data = match args.first() { + Some(Value::Bytes(bytes)) => bytes, + _ => bail!("decode function expects bytes as argument"), + }; + let selector = alloy::primitives::FixedBytes::<4>::from_slice(&data[..4]); + let function = abi + .functions() + .find(|f| f.selector() == selector) + .ok_or(anyhow::anyhow!( + "function with selector {} not found for {}", + selector, + name + ))?; + let decoded = function.abi_decode_input(&data[4..], true)?; + let values = decoded + .into_iter() + .map(Value::try_from) + .collect::>>()?; + Ok(Value::Tuple(vec![ + Value::Str(function.signature()), + Value::Tuple(values), + ])) +} + +fn abi_encode(_env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let arr = Value::Tuple(args.to_vec()); + let dyn_sol = DynSolValue::try_from(&arr)?; + let abi_encoded = dyn_sol.abi_encode(); + Ok(Value::Bytes(abi_encoded)) +} + +fn abi_encode_packed(_env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let arr = Value::Tuple(args.to_vec()); + let dyn_sol = DynSolValue::try_from(&arr)?; + let abi_encoded = dyn_sol.abi_encode_packed(); + Ok(Value::Bytes(abi_encoded)) +} + +lazy_static! { + pub static ref ABI_ENCODE: Arc = SyncMethod::arc("encode", abi_encode, vec![]); + pub static ref ABI_ENCODE_PACKED: Arc = + SyncMethod::arc("encodePacked", abi_encode_packed, vec![]); + pub static ref ABI_DECODE: Arc = SyncMethod::arc("decode", abi_decode, vec![]); + pub static ref ABI_DECODE_CALLDATA: Arc = SyncMethod::arc( + "decode", + abi_decode_calldata, + vec![vec![FunctionParam::new("calldata", Type::Bytes)]] + ); +} diff --git a/src/interpreter/builtins/address.rs b/src/interpreter/builtins/address.rs new file mode 100644 index 0000000..2da0ca4 --- /dev/null +++ b/src/interpreter/builtins/address.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; + +use alloy::transports::BoxFuture; +use anyhow::Result; +use futures::FutureExt; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{AsyncProperty, FunctionDef}, + Env, Value, +}; + +fn get_balance<'a>(env: &'a Env, receiver: &'a Value) -> BoxFuture<'a, Result> { + async move { + Ok(Value::Uint( + env.get_provider() + .get_balance(receiver.as_address()?) + .await?, + 256, + )) + } + .boxed() +} + +lazy_static! { + pub static ref ADDRESS_BALANCE: Arc = + AsyncProperty::arc("balance", get_balance); +} diff --git a/src/interpreter/builtins/block.rs b/src/interpreter/builtins/block.rs new file mode 100644 index 0000000..d54bcea --- /dev/null +++ b/src/interpreter/builtins/block.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use alloy::{eips::BlockId, rpc::types::BlockTransactionsKind}; +use anyhow::{anyhow, Ok, Result}; +use futures::{future::BoxFuture, FutureExt}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{AsyncProperty, FunctionDef}, + Env, Value, +}; + +fn get_chain_id<'a>(env: &'a Env, _arg: &'a Value) -> BoxFuture<'a, Result> { + async move { Ok(env.get_provider().get_chain_id().await?.into()) }.boxed() +} + +fn get_base_fee<'a>(env: &'a Env, _arg: &'a Value) -> BoxFuture<'a, Result> { + async move { Ok(env.get_provider().get_gas_price().await?.into()) }.boxed() +} + +fn get_block_number<'a>(env: &'a Env, _arg: &'a Value) -> BoxFuture<'a, Result> { + async move { Ok(env.get_provider().get_block_number().await?.into()) }.boxed() +} + +fn get_timestamp<'a>(env: &'a Env, _arg: &'a Value) -> BoxFuture<'a, Result> { + async move { + let latest_block = env + .get_provider() + .get_block(BlockId::latest(), BlockTransactionsKind::Hashes) + .await? + .ok_or(anyhow!("latest block not found"))?; + Ok(latest_block.header.timestamp.into()) + } + .boxed() +} + +lazy_static! { + pub static ref BLOCK_CHAIN_ID: Arc = + AsyncProperty::arc("chainid", get_chain_id); + pub static ref BLOCK_BASE_FEE: Arc = + AsyncProperty::arc("basefee", get_base_fee); + pub static ref BLOCK_NUMBER: Arc = + AsyncProperty::arc("number", get_block_number); + pub static ref BLOCK_TIMESTAMP: Arc = + AsyncProperty::arc("timestamp", get_timestamp); +} diff --git a/src/interpreter/builtins/concat.rs b/src/interpreter/builtins/concat.rs new file mode 100644 index 0000000..51aafce --- /dev/null +++ b/src/interpreter/builtins/concat.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use anyhow::{bail, Result}; +use futures::{future::BoxFuture, FutureExt}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{FunctionDef, FunctionParam}, + types::HashableIndexMap, + Env, Value, +}; + +fn concat_strings(string: String, args: &[Value]) -> Result { + if let Some(Value::Str(s)) = args.first() { + Ok(format!("{}{}", string, s)) + } else { + bail!("cannot concat {} with {:?}", string, args) + } +} + +fn concat_arrays(arr: Vec, args: &[Value]) -> Result> { + if let Some(Value::Array(other, _)) = args.first() { + let mut new_arr = arr.clone(); + new_arr.extend(other.clone()); + Ok(new_arr) + } else { + bail!("cannot concat {:?} with {:?}", arr, args) + } +} + +fn concat_bytes(bytes: Vec, args: &[Value]) -> Result> { + if let Some(Value::Bytes(other)) = args.first() { + let mut new_bytes = bytes.clone(); + new_bytes.extend(other.clone()); + Ok(new_bytes) + } else { + bail!("cannot concat {:?} with {:?}", bytes, args) + } +} + +fn concat(args: &[Value]) -> Result { + match args.first() { + Some(Value::Str(s)) => concat_strings(s.clone(), &args[1..]).map(Value::Str), + Some(Value::Array(arr, t)) => { + concat_arrays(arr.clone(), &args[1..]).map(|items| Value::Array(items, t.clone())) + } + Some(Value::Bytes(b)) => concat_bytes(b.clone(), &args[1..]).map(Value::Bytes), + _ => bail!("cannot concat {}", args[0]), + } +} + +#[derive(Debug)] +pub struct Concat; + +impl FunctionDef for Concat { + fn name(&self) -> &str { + "concat" + } + + fn get_valid_args(&self, receiver: &Option) -> Vec> { + receiver.as_ref().map_or(vec![], |r| { + vec![vec![FunctionParam::new("other", r.get_type().clone())]] + }) + } + + fn is_property(&self) -> bool { + false + } + + fn execute<'a>( + &'a self, + _env: &'a mut Env, + args: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async { concat(args) }.boxed() + } +} + +lazy_static! { + pub static ref CONCAT: Arc = Arc::new(Concat); +} diff --git a/src/interpreter/builtins/console.rs b/src/interpreter/builtins/console.rs new file mode 100644 index 0000000..d9e0498 --- /dev/null +++ b/src/interpreter/builtins/console.rs @@ -0,0 +1,18 @@ +use std::sync::Arc; + +use anyhow::Result; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{FunctionDef, SyncMethod}, + Env, Value, +}; + +fn log(_env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + args.iter().for_each(|arg| println!("{}", arg)); + Ok(Value::Null) +} + +lazy_static! { + pub static ref CONSOLE_LOG: Arc = SyncMethod::arc("log", log, vec![]); +} diff --git a/src/interpreter/builtins/format.rs b/src/interpreter/builtins/format.rs new file mode 100644 index 0000000..5d78276 --- /dev/null +++ b/src/interpreter/builtins/format.rs @@ -0,0 +1,156 @@ +use std::sync::Arc; + +use alloy::{ + hex, + primitives::{I256, U256}, +}; +use anyhow::Result; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{FunctionDef, FunctionParam, SyncFunction, SyncMethod}, + Env, Type, Value, +}; + +fn common_to_decimals( + value: T, + decimals: Option, + precision: Option, + to_f64: F, + pow: G, +) -> Result +where + T: Copy + std::ops::Div, + F: Fn(T) -> Result, + G: Fn(u32) -> T, +{ + let decimals = decimals.unwrap_or(18); + let precision = precision.unwrap_or(2); + let result = if decimals > precision { + let downscaled = value / pow((decimals - precision - 1) as u32); + match to_f64(downscaled) { + Ok(res) => Ok(res / 10f64.powi(precision + 1)), + _ => to_f64(value / pow(decimals as u32)), + } + } else { + to_f64(value / pow(decimals as u32)) + }; + result.map(|result| format!("{:.prec$}", result, prec = precision as usize)) +} + +fn uint_to_decimals(value: U256, decimals: Option, precision: Option) -> Result { + common_to_decimals( + value, + decimals, + precision, + |v: U256| Ok(TryInto::::try_into(v).map(|v| v as f64)?), + |exp| U256::from(10u64).pow(U256::from(exp)), + ) +} + +fn int_to_decimals(value: I256, decimals: Option, precision: Option) -> Result { + common_to_decimals( + value, + decimals, + precision, + |v: I256| Ok(TryInto::::try_into(v).map(|v| v as f64)?), + |exp| I256::from_raw(U256::from(10u64).pow(U256::from(exp))), + ) +} + +fn to_decimals(value: T, args: &[Value], func: F) -> Result +where + F: Fn(T, Option, Option) -> Result, +{ + let decimals = args.first().map(|v| v.as_i32()).transpose()?; + let precision = args.get(1).map(|v| v.as_i32()).transpose()?; + func(value, decimals, precision) +} + +fn format_bytes(bytes: &[u8]) -> String { + let mut stripped_bytes = bytes; + let last_0 = bytes.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1); + if last_0 > 0 { + stripped_bytes = &bytes[..last_0]; + } + let is_diplayable = bytes.iter().all(|c| c.is_ascii()); + if is_diplayable { + return String::from_utf8_lossy(stripped_bytes).to_string(); + } else { + format!("0x{}", hex::encode(bytes)) + } +} + +fn format(value: &Value, args: &[Value]) -> Result { + match value { + Value::Uint(n, _) => to_decimals(*n, args, uint_to_decimals), + Value::Int(n, _) => to_decimals(*n, args, int_to_decimals), + Value::Str(s) => Ok(s.clone()), + Value::Bytes(b) => Ok(format_bytes(b)), + Value::FixBytes(b, _) => Ok(format_bytes(&b.0)), + v => Ok(format!("{}", v)), + } + .map(Value::Str) +} + +fn format_method(_env: &mut Env, value: &Value, args: &[Value]) -> Result { + format(value, args) +} + +fn format_func(_env: &Env, args: &[Value]) -> Result { + format(&args[0], &args[1..]) +} + +lazy_static! { + pub static ref NUM_FORMAT: Arc = SyncMethod::arc( + "format", + format_method, + vec![ + vec![], + vec![FunctionParam::new("decimals", Type::Uint(8))], + vec![ + FunctionParam::new("decimals", Type::Uint(8)), + FunctionParam::new("precision", Type::Uint(8)) + ] + ] + ); + pub static ref NON_NUM_FORMAT: Arc = + SyncMethod::arc("format", format_method, vec![vec![]]); + pub static ref FORMAT_FUNCTION: Arc = SyncFunction::arc( + "format", + format_func, + vec![ + vec![FunctionParam::new("value", Type::Any)], + vec![ + FunctionParam::new("value", Type::Any), + FunctionParam::new("decimals", Type::Uint(8)) + ], + vec![ + FunctionParam::new("value", Type::Any), + FunctionParam::new("decimals", Type::Uint(8)), + FunctionParam::new("precision", Type::Uint(8)) + ] + ] + ); +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_uint_to_decimals() -> Result<()> { + let value = U256::from(10).pow(U256::from(18)); + assert_eq!(uint_to_decimals(value, None, None)?, "1.00"); + + assert_eq!( + uint_to_decimals(U256::from(12348000), Some(6), None)?, + "12.35" + ); + assert_eq!( + uint_to_decimals(U256::from(12348000), Some(6), Some(3))?, + "12.348" + ); + + Ok(()) + } +} diff --git a/src/interpreter/builtins/iterable.rs b/src/interpreter/builtins/iterable.rs new file mode 100644 index 0000000..94a8128 --- /dev/null +++ b/src/interpreter/builtins/iterable.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; + +use anyhow::{bail, Result}; +use futures::{future::BoxFuture, FutureExt}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{AsyncMethod, FunctionDef, FunctionParam, SyncProperty}, + Env, Type, Value, +}; + +fn map<'a>( + env: &'a mut Env, + receiver: &'a Value, + args: &'a [Value], +) -> BoxFuture<'a, Result> { + async move { + let mut values = vec![]; + for v in receiver.get_items()? { + let value = match args.first() { + Some(Value::Func(func)) => func.execute(env, &[v.clone()]).await?, + Some(Value::TypeObject(type_)) => type_.cast(&v)?, + _ => bail!("map function expects a function or type as an argument"), + }; + values.push(value); + } + match receiver.get_type() { + Type::Tuple(_) => Ok(Value::Tuple(values)), + Type::Array(t) => Ok(Value::Array(values, t.clone())), + ty => bail!("cannot map to type {}", ty), + } + } + .boxed() +} + +pub fn iter_len(_env: &Env, arg: &Value) -> Result { + arg.len().map(Into::into) +} + +lazy_static! { + pub static ref ITER_MAP: Arc = AsyncMethod::arc( + "map", + map, + vec![vec![FunctionParam::new("f", Type::Function)]] + ); + pub static ref ITER_LEN: Arc = SyncProperty::arc("length", iter_len); +} diff --git a/src/interpreter/builtins/misc.rs b/src/interpreter/builtins/misc.rs new file mode 100644 index 0000000..d79640f --- /dev/null +++ b/src/interpreter/builtins/misc.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; + +use anyhow::{anyhow, bail, Result}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{FunctionDef, FunctionParam, SyncFunction, SyncProperty}, + Env, Type, Value, +}; + +fn keccak256(_env: &Env, args: &[Value]) -> Result { + let data = match args.first() { + Some(Value::Bytes(data)) => data, + _ => bail!("keccak256 function expects bytes as an argument"), + }; + Ok(Value::FixBytes(alloy::primitives::keccak256(data), 32)) +} + +fn get_type<'a>(_env: &Env, args: &[Value]) -> Result { + args.first() + .map(|v| Value::TypeObject(v.get_type())) + .ok_or(anyhow!("get_type function expects one argument")) +} + +fn mapping_keys(_env: &Env, receiver: &Value) -> Result { + match receiver { + Value::Mapping(mapping, key_type, _) => { + let keys = mapping.0.keys().cloned().collect(); + Ok(Value::Array(keys, key_type.clone())) + } + _ => bail!("mapping_keys function expects a mapping as an argument"), + } +} + +lazy_static! { + pub static ref KECCAK256: Arc = SyncFunction::arc( + "keccak256", + keccak256, + vec![vec![FunctionParam::new("data", Type::Bytes)]] + ); + pub static ref GET_TYPE: Arc = SyncFunction::arc( + "type", + get_type, + vec![vec![FunctionParam::new("value", Type::Any)]] + ); + pub static ref MAPPING_KEYS: Arc = SyncProperty::arc("keys", mapping_keys); +} diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs new file mode 100644 index 0000000..49960c7 --- /dev/null +++ b/src/interpreter/builtins/mod.rs @@ -0,0 +1,162 @@ +use iterable::ITER_LEN; +use std::collections::HashMap; +use std::sync::Arc; + +use lazy_static::lazy_static; + +mod abi; +mod address; +mod block; +mod concat; +mod console; +mod format; +mod iterable; +mod misc; +mod numeric; +mod receipt; +mod repl; + +use crate::interpreter::functions::Function; +use crate::interpreter::functions::FunctionDef; +use crate::interpreter::types::NonParametricType; +use crate::interpreter::Type; +use crate::interpreter::Value; + +lazy_static! { + pub static ref VALUES: HashMap = { + let mut m = HashMap::new(); + + m.insert("repl".to_string(), Value::TypeObject(Type::Repl)); + m.insert("console".to_string(), Value::TypeObject(Type::Console)); + m.insert("block".to_string(), Value::TypeObject(Type::Block)); + m.insert( + "Transaction".to_string(), + Value::TypeObject(Type::Transaction), + ); + m.insert("abi".to_string(), Value::TypeObject(Type::Abi)); + + let funcs: Vec<(&str, Arc)> = vec![ + ("format", format::FORMAT_FUNCTION.clone()), + ("keccak256", misc::KECCAK256.clone()), + ("type", misc::GET_TYPE.clone()), + ]; + for (name, func) in funcs { + m.insert( + name.to_string(), + Value::Func(Box::new(Function::new(func, None))), + ); + } + + m + }; + pub static ref INSTANCE_METHODS: HashMap>> = { + let mut m = HashMap::new(); + + let mut string_methods = HashMap::new(); + + string_methods.insert("length".to_string(), iterable::ITER_LEN.clone()); + string_methods.insert("concat".to_string(), concat::CONCAT.clone()); + string_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + m.insert(NonParametricType::String, string_methods); + + let mut array_methods = HashMap::new(); + array_methods.insert("length".to_string(), ITER_LEN.clone()); + array_methods.insert("map".to_string(), iterable::ITER_MAP.clone()); + array_methods.insert("concat".to_string(), concat::CONCAT.clone()); + array_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + m.insert(NonParametricType::Array, array_methods); + + let mut bytes_methods = HashMap::new(); + bytes_methods.insert("length".to_string(), iterable::ITER_LEN.clone()); + bytes_methods.insert("map".to_string(), iterable::ITER_MAP.clone()); + bytes_methods.insert("concat".to_string(), concat::CONCAT.clone()); + bytes_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + m.insert(NonParametricType::Bytes, bytes_methods); + + let mut fix_bytes_methods = HashMap::new(); + fix_bytes_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + m.insert(NonParametricType::FixBytes, fix_bytes_methods); + + let mut num_methods = HashMap::new(); + num_methods.insert("format".to_string(), format::NUM_FORMAT.clone()); + num_methods.insert("mul".to_string(), numeric::NUM_MUL.clone()); + num_methods.insert("div".to_string(), numeric::NUM_DIV.clone()); + for types in [NonParametricType::Int, NonParametricType::Uint] { + m.insert(types, num_methods.clone()); + } + + let mut addr_methods = HashMap::new(); + addr_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + addr_methods.insert("balance".to_string(), address::ADDRESS_BALANCE.clone()); + m.insert(NonParametricType::Address, addr_methods); + + let mut transaction_methods = HashMap::new(); + transaction_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + transaction_methods.insert("getReceipt".to_string(), receipt::TX_GET_RECEIPT.clone()); + m.insert(NonParametricType::Transaction, transaction_methods); + + let mut mapping_methods = HashMap::new(); + mapping_methods.insert("format".to_string(), format::NON_NUM_FORMAT.clone()); + mapping_methods.insert("keys".to_string(), misc::MAPPING_KEYS.clone()); + m.insert(NonParametricType::Mapping, mapping_methods); + + m + }; + pub static ref STATIC_METHODS: HashMap>> = { + let mut m = HashMap::new(); + + let mut contract_methods = HashMap::new(); + contract_methods.insert("decode".to_string(), abi::ABI_DECODE_CALLDATA.clone()); + m.insert(NonParametricType::Contract, contract_methods); + + let mut abi_methods = HashMap::new(); + abi_methods.insert("encode".to_string(), abi::ABI_ENCODE.clone()); + abi_methods.insert("encodePacked".to_string(), abi::ABI_ENCODE_PACKED.clone()); + abi_methods.insert("decode".to_string(), abi::ABI_DECODE.clone()); + m.insert(NonParametricType::Abi, abi_methods); + + let mut block_methods = HashMap::new(); + block_methods.insert("chainid".to_string(), block::BLOCK_CHAIN_ID.clone()); + block_methods.insert("basefee".to_string(), block::BLOCK_BASE_FEE.clone()); + block_methods.insert("number".to_string(), block::BLOCK_NUMBER.clone()); + block_methods.insert("timestamp".to_string(), block::BLOCK_TIMESTAMP.clone()); + m.insert(NonParametricType::Block, block_methods); + + let mut console_methods = HashMap::new(); + console_methods.insert("log".to_string(), console::CONSOLE_LOG.clone()); + m.insert(NonParametricType::Console, console_methods); + + let mut repl_methods = HashMap::new(); + repl_methods.insert("vars".to_string(), repl::REPL_LIST_VARS.clone()); + repl_methods.insert("types".to_string(), repl::REPL_LIST_TYPES.clone()); + repl_methods.insert("connected".to_string(), repl::REPL_IS_CONNECTED.clone()); + repl_methods.insert("rpc".to_string(), repl::REPL_RPC.clone()); + repl_methods.insert("debug".to_string(), repl::REPL_DEBUG.clone()); + repl_methods.insert("exec".to_string(), repl::REPL_EXEC.clone()); + repl_methods.insert("loadAbi".to_string(), repl::REPL_LOAD_ABI.clone()); + repl_methods.insert("fetchAbi".to_string(), repl::REPL_FETCH_ABI.clone()); + repl_methods.insert("account".to_string(), repl::REPL_ACCOUNT.clone()); + repl_methods.insert( + "loadPrivateKey".to_string(), + repl::REPL_LOAD_PRIVATE_KEY.clone(), + ); + repl_methods.insert( + "listLedgerWallets".to_string(), + repl::REPL_LIST_LEDGER_WALLETS.clone(), + ); + repl_methods.insert("loadLedger".to_string(), repl::REPL_LOAD_LEDGER.clone()); + m.insert(NonParametricType::Repl, repl_methods); + + m + }; + pub static ref TYPE_METHODS: HashMap>> = { + let mut m = HashMap::new(); + let mut num_methods = HashMap::new(); + num_methods.insert("max".to_string(), numeric::TYPE_MAX.clone()); + num_methods.insert("min".to_string(), numeric::TYPE_MIN.clone()); + for type_ in [NonParametricType::Int, NonParametricType::Uint] { + m.insert(type_, num_methods.clone()); + } + m + }; +} diff --git a/src/interpreter/builtins/numeric.rs b/src/interpreter/builtins/numeric.rs new file mode 100644 index 0000000..9872090 --- /dev/null +++ b/src/interpreter/builtins/numeric.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use anyhow::{bail, Result}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{FunctionDef, FunctionParam, SyncMethod, SyncProperty}, + Env, Type, Value, +}; + +fn mul_div_args(args: &[Value]) -> Result<(Value, u64)> { + match args { + [v2] => Ok((v2.clone(), 18)), + [v2, d] => Ok((v2.clone(), d.as_u64()?)), + _ => bail!("mul function expects one or two arguments"), + } +} + +fn mul(_env: &mut Env, receiver: &Value, args: &[Value]) -> Result { + let (value, decimals) = mul_div_args(args)?; + (receiver.clone() * value.clone())? / Value::decimal_multiplier(decimals as u8) +} + +fn div(_env: &mut Env, receiver: &Value, args: &[Value]) -> Result { + let (value, decimals) = mul_div_args(args)?; + (receiver.clone() * Value::decimal_multiplier(decimals as u8))? / value.clone() +} + +fn type_min(_env: &Env, receiver: &Value) -> Result { + receiver.get_type().min().map(Into::into) +} + +fn type_max(_env: &Env, receiver: &Value) -> Result { + receiver.get_type().max().map(Into::into) +} + +lazy_static! { + pub static ref NUM_MUL: Arc = SyncMethod::arc( + "mul", + mul, + vec![ + vec![FunctionParam::new("factor", Type::Uint(256))], + vec![ + FunctionParam::new("factor", Type::Uint(256)), + FunctionParam::new("decimals", Type::Uint(8)) + ] + ] + ); + pub static ref NUM_DIV: Arc = SyncMethod::arc( + "div", + div, + vec![ + vec![FunctionParam::new("divisor", Type::Uint(256))], + vec![ + FunctionParam::new("divisor", Type::Uint(256)), + FunctionParam::new("decimals", Type::Uint(8)) + ] + ] + ); + pub static ref TYPE_MAX: Arc = SyncProperty::arc("max", type_max); + pub static ref TYPE_MIN: Arc = SyncProperty::arc("max", type_min); +} diff --git a/src/interpreter/builtins/receipt.rs b/src/interpreter/builtins/receipt.rs new file mode 100644 index 0000000..bb825d3 --- /dev/null +++ b/src/interpreter/builtins/receipt.rs @@ -0,0 +1,45 @@ +use std::sync::Arc; + +use alloy::providers::PendingTransactionBuilder; +use anyhow::{bail, Result}; +use futures::{future::BoxFuture, FutureExt}; +use lazy_static::lazy_static; + +use crate::interpreter::{ + functions::{AsyncMethod, FunctionDef, FunctionParam}, + Env, Type, Value, +}; + +fn wait_for_receipt<'a>( + env: &'a mut Env, + receiver: &'a Value, + args: &'a [Value], +) -> BoxFuture<'a, Result> { + async move { + let tx = match receiver { + Value::Transaction(tx) => *tx, + _ => bail!("wait_for_receipt function expects a transaction as argument"), + }; + let provider = env.get_provider(); + let tx = PendingTransactionBuilder::new(provider.root(), tx); + if args.len() > 1 { + bail!("get_receipt function expects at most one argument") + } + let timeout = args.first().map_or(Ok(30), |v| v.as_u64())?; + let receipt = tx + .with_required_confirmations(1) + .with_timeout(Some(std::time::Duration::from_secs(timeout))) + .get_receipt() + .await?; + Ok(Value::TransactionReceipt(receipt.into())) + } + .boxed() +} + +lazy_static! { + pub static ref TX_GET_RECEIPT: Arc = AsyncMethod::arc( + "getReceipt", + wait_for_receipt, + vec![vec![], vec![FunctionParam::new("timeout", Type::Uint(256))]] + ); +} diff --git a/src/interpreter/builtins/repl.rs b/src/interpreter/builtins/repl.rs new file mode 100644 index 0000000..c191976 --- /dev/null +++ b/src/interpreter/builtins/repl.rs @@ -0,0 +1,232 @@ +use std::{process::Command, sync::Arc}; + +use alloy::providers::Provider; +use anyhow::{anyhow, bail, Ok, Result}; +use futures::{future::BoxFuture, FutureExt}; +use lazy_static::lazy_static; + +use crate::{ + interpreter::{ + functions::{ + AsyncMethod, AsyncProperty, FunctionDef, FunctionParam, SyncMethod, SyncProperty, + }, + ContractInfo, Env, Type, Value, + }, + loaders, +}; + +fn list_vars(env: &Env, _receiver: &Value) -> Result { + let mut vars = env.list_vars(); + vars.sort(); + for k in vars.iter() { + println!("{}: {}", k, env.get_var(k).unwrap()); + } + Ok(Value::Null) +} + +fn list_types(env: &Env, _receiver: &Value) -> Result { + let mut types = env.list_types(); + types.sort(); + for k in types.iter() { + println!("{}", k); + } + Ok(Value::Null) +} + +fn is_connected<'a>(env: &'a Env, _receiver: &'a Value) -> BoxFuture<'a, Result> { + async move { + let res = env.get_provider().root().get_chain_id().await.is_ok(); + Ok(Value::Bool(res)) + } + .boxed() +} + +fn rpc(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + match args { + [] => Ok(Value::Str(env.get_rpc_url())), + [url] => { + env.set_provider_url(&url.as_string()?)?; + Ok(Value::Null) + } + _ => bail!("rpc: invalid arguments"), + } +} + +fn debug(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + match args { + [] => Ok(Value::Bool(env.is_debug())), + [Value::Bool(b)] => { + env.set_debug(*b); + Ok(Value::Null) + } + _ => bail!("debug: invalid arguments"), + } +} + +fn exec(_env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let cmd = args + .first() + .ok_or(anyhow!("exec: missing command"))? + .as_string()?; + + let splitted = cmd.split_whitespace().collect::>(); + let mut cmd = Command::new(splitted[0]).args(&splitted[1..]).spawn()?; + let res = cmd.wait()?; + let code = res.code().ok_or(anyhow!("exec: command failed"))?; + Ok(code.into()) +} + +fn load_abi(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let (name, filepath, key) = match args { + [Value::Str(name), Value::Str(filepath)] => (name, filepath, None), + [Value::Str(name), Value::Str(filepath), Value::Str(key)] => { + (name, filepath, Some(key.as_str())) + } + _ => bail!("loadAbi: invalid arguments"), + }; + let abi = loaders::file::load_abi(filepath, key)?; + let contract_info = ContractInfo(name.to_string(), abi); + env.set_type(name, Type::Contract(contract_info.clone())); + Ok(Value::Null) +} + +fn fetch_abi<'a>( + env: &'a mut Env, + _receiver: &'a Value, + args: &'a [Value], +) -> BoxFuture<'a, Result> { + async move { + match args { + [Value::Str(name), Value::Addr(address)] => { + let chain_id = env.get_chain_id().await?; + let etherscan_config = env.config.get_etherscan_config(chain_id)?; + let abi = + loaders::etherscan::load_abi(etherscan_config, &address.to_string()).await?; + let contract_info = ContractInfo(name.to_string(), abi); + env.set_type(name, Type::Contract(contract_info.clone())); + Ok(Value::Contract(contract_info, *address)) + } + _ => bail!("fetchAbi: invalid arguments"), + } + } + .boxed() +} + +fn get_account(env: &Env, _receiver: &Value) -> Result { + let account = env.get_default_sender(); + Ok(account.map(Value::Addr).unwrap_or(Value::Null)) +} + +fn get_default_sender(env: &Env) -> Value { + env.get_default_sender() + .map(Value::Addr) + .unwrap_or(Value::Null) +} + +fn load_private_key(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let key = match args { + [Value::Str(key)] => key.clone(), + [] => rpassword::prompt_password("Enter private key: ")?, + _ => bail!("loadPrivateKey: invalid arguments"), + }; + env.set_private_key(key.as_str())?; + Ok(get_default_sender(env)) +} + +fn list_ledgers<'a>( + env: &'a mut Env, + _receiver: &Value, + args: &'a [Value], +) -> BoxFuture<'a, Result> { + async move { + let count = match args { + [] => 5, + [value] => value.as_usize()?, + _ => bail!("listLedgerWallets: invalid arguments"), + }; + let wallets = env.list_ledger_wallets(count).await?; + Ok(Value::Array( + wallets.into_iter().map(Value::Addr).collect(), + Box::new(Type::Address), + )) + } + .boxed() +} + +fn load_ledger<'a>( + env: &'a mut Env, + _receiver: &'a Value, + args: &'a [Value], +) -> BoxFuture<'a, Result> { + async move { + let index = match args { + [] => 0, + [value] => value.as_usize()?, + _ => bail!("loadLedger: invalid arguments"), + }; + env.load_ledger(index).await?; + Ok(get_default_sender(env)) + } + .boxed() +} + +lazy_static! { + pub static ref REPL_LIST_VARS: Arc = SyncProperty::arc("vars", list_vars); + pub static ref REPL_LIST_TYPES: Arc = SyncProperty::arc("types", list_types); + pub static ref REPL_IS_CONNECTED: Arc = + AsyncProperty::arc("connected", is_connected); + pub static ref REPL_RPC: Arc = SyncMethod::arc( + "rpc", + rpc, + vec![vec![], vec![FunctionParam::new("url", Type::String)]] + ); + pub static ref REPL_DEBUG: Arc = SyncMethod::arc( + "debug", + debug, + vec![vec![], vec![FunctionParam::new("debug", Type::Bool)]] + ); + pub static ref REPL_EXEC: Arc = SyncMethod::arc( + "exec", + exec, + vec![vec![FunctionParam::new("command", Type::String)]] + ); + pub static ref REPL_LOAD_ABI: Arc = SyncMethod::arc( + "loadAbi", + load_abi, + vec![ + vec![ + FunctionParam::new("name", Type::String), + FunctionParam::new("filepath", Type::String) + ], + vec![ + FunctionParam::new("name", Type::String), + FunctionParam::new("filepath", Type::String), + FunctionParam::new("jsonKey", Type::String) + ] + ] + ); + pub static ref REPL_FETCH_ABI: Arc = AsyncMethod::arc( + "fetchAbi", + fetch_abi, + vec![vec![ + FunctionParam::new("name", Type::String), + FunctionParam::new("address", Type::Address) + ]] + ); + pub static ref REPL_ACCOUNT: Arc = SyncProperty::arc("account", get_account); + pub static ref REPL_LOAD_PRIVATE_KEY: Arc = SyncMethod::arc( + "loadPrivateKey", + load_private_key, + vec![vec![], vec![FunctionParam::new("privateKey", Type::String)]] + ); + pub static ref REPL_LIST_LEDGER_WALLETS: Arc = AsyncMethod::arc( + "listLedgerWallets", + list_ledgers, + vec![vec![], vec![FunctionParam::new("count", Type::Uint(256))]] + ); + pub static ref REPL_LOAD_LEDGER: Arc = AsyncMethod::arc( + "loadLedger", + load_ledger, + vec![vec![], vec![FunctionParam::new("index", Type::Uint(256))]] + ); +} diff --git a/src/interpreter/directive.rs b/src/interpreter/directive.rs deleted file mode 100644 index 37d3fd0..0000000 --- a/src/interpreter/directive.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::process::Command; - -use alloy::providers::Provider; -use anyhow::{bail, Result}; - -use crate::loaders; - -use super::{ContractInfo, Env, Type, Value}; - -#[derive(Debug, PartialEq, Clone, Hash, Eq)] -pub enum Directive { - ListVars, - ListTypes, - Rpc, - Debug, - Exec, - LoadAbi, - FetchAbi, - Connected, - Account, - LoadPrivateKey, - LoadLedger, - ListLedgerWallets, -} - -impl std::fmt::Display for Directive { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Directive::ListVars => write!(f, "vars"), - Directive::ListTypes => write!(f, "types"), - Directive::Rpc => write!(f, "rpc"), - Directive::Debug => write!(f, "debug"), - Directive::Exec => write!(f, "exec"), - Directive::Connected => write!(f, "connected"), - Directive::LoadAbi => write!(f, "loadAbi"), - Directive::FetchAbi => write!(f, "fetchAbi"), - Directive::LoadPrivateKey => write!(f, "loadPrivateKey"), - Directive::Account => write!(f, "account"), - Directive::LoadLedger => write!(f, "loadLedger"), - Directive::ListLedgerWallets => write!(f, "listLedgerWallets"), - } - } -} - -fn list_vars(env: &Env) { - let mut vars = env.list_vars(); - vars.sort(); - for k in vars.iter() { - println!("{}: {}", k, env.get_var(k).unwrap()); - } -} - -fn list_types(env: &Env) { - let mut types = env.list_types(); - types.sort(); - for k in types.iter() { - println!("{}", k); - } -} - -impl Directive { - pub fn all() -> Vec { - vec![ - Directive::ListVars, - Directive::ListTypes, - Directive::Rpc, - Directive::Debug, - Directive::Exec, - Directive::LoadAbi, - Directive::FetchAbi, - Directive::Connected, - Directive::Account, - Directive::LoadPrivateKey, - Directive::LoadLedger, - Directive::ListLedgerWallets, - ] - } - - pub fn is_property(&self) -> bool { - matches!( - self, - Directive::Connected | Directive::ListVars | Directive::ListTypes | Directive::Account - ) - } - - pub async fn execute(&self, args: &[Value], env: &mut Env) -> Result { - match self { - Directive::ListVars => list_vars(env), - Directive::ListTypes => list_types(env), - Directive::Connected => { - let res = env.get_provider().root().get_chain_id().await.is_ok(); - return Ok(Value::Bool(res)); - } - Directive::Rpc => match args { - [] => println!("{}", env.get_provider().root().client().transport().url()), - [url] => env.set_provider_url(&url.as_string()?)?, - _ => bail!("rpc: invalid arguments"), - }, - Directive::Debug => match args { - [] => return Ok(Value::Bool(env.is_debug())), - [Value::Bool(b)] => env.set_debug(*b), - _ => bail!("debug: invalid arguments"), - }, - Directive::Exec => match args { - [Value::Str(cmd)] => { - let splitted = cmd.split_whitespace().collect::>(); - Command::new(splitted[0]).args(&splitted[1..]).spawn()?; - } - _ => bail!("exec: invalid arguments"), - }, - Directive::LoadAbi => { - let (name, filepath, key) = match args { - [Value::Str(name), Value::Str(filepath)] => (name, filepath, None), - [Value::Str(name), Value::Str(filepath), Value::Str(key)] => { - (name, filepath, Some(key.as_str())) - } - _ => bail!("fetchAbi: invalid arguments"), - }; - let abi = loaders::file::load_abi(filepath, key)?; - let contract_info = ContractInfo(name.to_string(), abi); - env.set_type(name, Type::Contract(contract_info.clone())); - } - Directive::FetchAbi => match args { - [Value::Str(name), Value::Addr(address)] => { - let chain_id = env.get_chain_id().await?; - let etherscan_config = env.config.get_etherscan_config(chain_id)?; - let abi = loaders::etherscan::load_abi(etherscan_config, &address.to_string()) - .await?; - let contract_info = ContractInfo(name.to_string(), abi); - env.set_type(name, Type::Contract(contract_info.clone())); - return Ok(Value::Contract(contract_info, *address)); - } - _ => bail!("fetchAbi: invalid arguments"), - }, - Directive::Account => { - let account = env.get_default_sender(); - return Ok(account.map(Value::Addr).unwrap_or(Value::Null)); - } - Directive::LoadPrivateKey => match args { - [Value::Str(key)] => { - env.set_private_key(key.as_str())?; - return Ok(self.get_default_sender(env)); - } - [] => { - let key = rpassword::prompt_password("Enter private key: ")?; - env.set_private_key(key.as_str())?; - return Ok(self.get_default_sender(env)); - } - _ => bail!("loadPrivateKey: invalid arguments"), - }, - Directive::ListLedgerWallets => { - let count = match args { - [] => 5, - [value] => value.as_usize()?, - _ => bail!("listLedgerWallets: invalid arguments"), - }; - let wallets = env.list_ledger_wallets(count).await?; - return Ok(Value::Array(wallets.into_iter().map(Value::Addr).collect())); - } - Directive::LoadLedger => { - let index = match args { - [] => 0, - [value] => value.as_usize()?, - _ => bail!("loadLedger: invalid arguments"), - }; - env.load_ledger(index).await?; - return Ok(self.get_default_sender(env)); - } - } - - Ok(Value::Null) - } - - fn get_default_sender(&self, env: &Env) -> Value { - env.get_default_sender() - .map(Value::Addr) - .unwrap_or(Value::Null) - } - - pub fn from_name(name: &str) -> Result { - match name { - "vars" => Ok(Directive::ListVars), - "types" => Ok(Directive::ListTypes), - "rpc" => Ok(Directive::Rpc), - "debug" => Ok(Directive::Debug), - "exec" => Ok(Directive::Exec), - "connected" => Ok(Directive::Connected), - "account" => Ok(Directive::Account), - "loadAbi" => Ok(Directive::LoadAbi), - "fetchAbi" => Ok(Directive::FetchAbi), - "loadPrivateKey" => Ok(Directive::LoadPrivateKey), - "listLedgerWallets" => Ok(Directive::ListLedgerWallets), - "loadLedger" => Ok(Directive::LoadLedger), - _ => Err(anyhow::anyhow!("Invalid directive")), - } - } -} diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index 06c0407..6146edb 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -1,5 +1,5 @@ use futures_util::lock::Mutex; -use solang_parser::pt::{Expression, Identifier}; +use solang_parser::pt::{Expression, Identifier, Statement}; use std::{ collections::{HashMap, HashSet}, sync::Arc, @@ -24,6 +24,7 @@ pub struct Env { variables: Vec>, types: HashMap, provider: Arc, Ethereum>>, + function_bodies: HashMap, wallet: Option, ledger: Option>>, pub config: Config, @@ -39,12 +40,21 @@ impl Env { variables: vec![HashMap::new()], types: HashMap::new(), provider: Arc::new(provider), + function_bodies: HashMap::new(), wallet: None, ledger: None, config, } } + pub fn set_function_body(&mut self, name: &str, body: Statement) { + self.function_bodies.insert(name.to_string(), body); + } + + pub fn get_function_body(&self, name: &str) -> Option<&Statement> { + self.function_bodies.get(name) + } + pub fn push_scope(&mut self) { self.variables.push(HashMap::new()); } diff --git a/src/interpreter/functions.rs b/src/interpreter/functions.rs deleted file mode 100644 index 7d6cd4b..0000000 --- a/src/interpreter/functions.rs +++ /dev/null @@ -1,461 +0,0 @@ -use std::fmt::Display; - -use alloy::{ - contract::{CallBuilder, ContractInstance, Interface}, - dyn_abi::Specifier, - json_abi::StateMutability, - network::{Network, TransactionBuilder}, - primitives::Address, - providers::Provider, - rpc::types::{TransactionInput, TransactionRequest}, - transports::Transport, -}; -use anyhow::{anyhow, bail, Result}; -use solang_parser::pt::{Expression, Identifier, Parameter, Statement}; - -use super::{ - builtin_functions::BuiltinFunction, evaluate_statement, types::ContractInfo, Env, - StatementResult, Type, Value, -}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FunctionParam { - name: String, - type_: Option, -} - -impl Display for FunctionParam { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.type_ { - Some(t) => write!(f, "{} {}", self.name, t), - None => write!(f, "{}", self.name), - } - } -} - -impl TryFrom for FunctionParam { - type Error = anyhow::Error; - - fn try_from(p: Parameter) -> Result { - match (p.name, p.ty) { - (Some(Identifier { name, .. }), Expression::Type(_, t)) => { - let type_ = Some(t.try_into()?); - Ok(FunctionParam { name, type_ }) - } - (None, Expression::Variable(Identifier { name, .. })) => { - Ok(FunctionParam { name, type_: None }) - } - _ => bail!("require param name or type and name"), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UserDefinedFunction { - pub name: String, - params: Vec, - body: Statement, -} - -impl std::hash::Hash for UserDefinedFunction { - fn hash(&self, state: &mut H) { - self.name.hash(state); - self.params.hash(state); - } -} - -impl TryFrom for UserDefinedFunction { - type Error = anyhow::Error; - - fn try_from(f: solang_parser::pt::FunctionDefinition) -> Result { - let name = f.name.clone().ok_or(anyhow!("require function name"))?.name; - let stmt = f.body.clone().ok_or(anyhow!("require function body"))?; - let params = f - .params - .iter() - .map(|(_, p)| { - p.clone() - .ok_or(anyhow!("require param")) - .and_then(FunctionParam::try_from) - }) - .collect::>>()?; - Ok(UserDefinedFunction { - name, - params, - body: stmt, - }) - } -} - -#[derive(Debug, Clone, PartialEq, Hash, Eq)] -pub enum ContractCallMode { - Default, - Encode, - Call, - Send, -} - -impl TryFrom<&str> for ContractCallMode { - type Error = anyhow::Error; - - fn try_from(s: &str) -> Result { - match s { - "encode" => Ok(ContractCallMode::Encode), - "call" => Ok(ContractCallMode::Call), - "send" => Ok(ContractCallMode::Send), - _ => bail!("{} does not exist for contract call", s), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct ContractCall { - info: ContractInfo, - addr: Address, - func_name: String, - mode: ContractCallMode, - options: CallOptions, -} - -impl ContractCall { - pub fn new(info: ContractInfo, addr: Address, func_name: String) -> Self { - ContractCall { - info, - addr, - func_name, - mode: ContractCallMode::Default, - options: CallOptions::default(), - } - } - - pub fn with_options(self, options: CallOptions) -> Self { - ContractCall { options, ..self } - } - - pub fn with_mode(self, mode: ContractCallMode) -> Self { - ContractCall { mode, ..self } - } -} - -#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)] -pub struct CallOptions { - value: Option>, -} - -impl Display for CallOptions { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(v) = &self.value { - write!(f, "value: {}", v) - } else { - write!(f, "") - } - } -} - -impl TryFrom for CallOptions { - type Error = anyhow::Error; - - fn try_from(value: Value) -> std::result::Result { - match value { - Value::NamedTuple(_, m) => { - let mut opts = CallOptions::default(); - for (k, v) in m.0.iter() { - match k.as_str() { - "value" => opts.value = Some(Box::new(v.clone())), - _ => bail!("unexpected key {}", k), - } - } - Ok(opts) - } - _ => bail!("expected indexed map but got {}", value), - } - } -} - -impl TryFrom for CallOptions { - type Error = anyhow::Error; - - fn try_from(value: StatementResult) -> std::result::Result { - match value { - StatementResult::Value(v) => v.try_into(), - _ => bail!("expected indexed map but got {}", value), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum Function { - ContractCall(ContractCall), - Builtin(BuiltinFunction), - UserDefined(UserDefinedFunction), - FieldAccess(Box, String), -} - -impl Display for Function { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Function::ContractCall(ContractCall { - info: ContractInfo(name, abi), - addr, - func_name, - mode, - options, - }) => { - let arg_types = abi - .function(func_name) - .map(|f| { - f[0].inputs - .iter() - .map(|t| t.to_string()) - .collect::>() - }) - .unwrap_or_default(); - let suffix = if mode == &ContractCallMode::Encode { - ".encode" - } else { - "" - }; - write!(f, "{}({}).{}", name, addr, func_name)?; - let formatted_options = format!("{}", options); - if !formatted_options.is_empty() { - write!(f, "{{{}}}", formatted_options)?; - } - write!(f, "({}){}", arg_types.join(","), suffix) - } - Function::FieldAccess(v, n) => write!(f, "{}.{}", v, n), - Function::Builtin(m) => write!(f, "{}", m), - Function::UserDefined(func) => { - let formatted_params = func - .params - .iter() - .map(|p| format!("{}", p)) - .collect::>() - .join(", "); - write!(f, "{}({})", func.name, formatted_params) - } - } - } -} - -impl Function { - pub fn with_opts(self, opts: CallOptions) -> Self { - match self { - Function::ContractCall(call) => Function::ContractCall(call.with_options(opts)), - v => v, - } - } - - pub fn with_receiver(receiver: &Value, name: &str) -> Result { - let func = match receiver { - Value::Contract(c, addr) => c.create_call(name, *addr)?, - v @ Value::NamedTuple(..) => { - Function::FieldAccess(Box::new(v.clone()), name.to_string()) - } - - Value::Func(Function::ContractCall(call)) => { - Function::ContractCall(call.clone().with_mode(ContractCallMode::try_from(name)?)) - } - - v => { - let method = BuiltinFunction::with_receiver(v, name)?; - Function::Builtin(method) - } - }; - Ok(func) - } - - pub async fn execute_in_current_scope(&self, args: &[Value], env: &mut Env) -> Result { - match self { - Function::ContractCall(call) => { - self._execute_contract_interaction(call, args, env).await - } - Function::FieldAccess(f, v) => f.get_field(v), - Function::Builtin(m) => m.execute(args, env).await, - Function::UserDefined(func) => { - if args.len() != func.params.len() { - bail!( - "function {} expect {} arguments, but got {}", - func.name, - func.params.len(), - args.len() - ); - } - for (param, arg) in func.params.iter().zip(args.iter()) { - if let Some(type_) = param.type_.clone() { - if type_ != arg.get_type() { - bail!( - "function {} expect {} to be {}, but got {}", - func.name, - param.name, - type_, - arg.get_type() - ); - } - } - env.set_var(¶m.name, arg.clone()); - } - evaluate_statement(env, Box::new(func.body.clone())) - .await - .map(|v| v.value().cloned().unwrap_or(Value::Null)) - } - } - } - - pub fn is_property(&self) -> bool { - match self { - Function::ContractCall(_) => false, - Function::FieldAccess(_, _) => true, - Function::Builtin(m) => m.is_property(), - Function::UserDefined(_) => false, - } - } - - pub async fn execute(&self, args: &[Value], env: &mut Env) -> Result { - env.push_scope(); - let result = self.execute_in_current_scope(args, env).await; - env.pop_scope(); - result - } - - async fn _execute_contract_interaction( - &self, - call: &ContractCall, - args: &[Value], - env: &Env, - ) -> Result { - let ContractInfo(name, abi) = &call.info; - let funcs = abi.function(&call.func_name).ok_or(anyhow!( - "function {} not found in {}", - call.func_name, - name - ))?; - let contract = ContractInstance::new( - call.addr, - env.get_provider().root().clone(), - Interface::new(abi.clone()), - ); - let mut call_result = Ok(Value::Null); - for func_abi in funcs.iter() { - let types = func_abi - .inputs - .iter() - .map(|t| t.resolve().map(Type::from).map_err(|e| anyhow!(e))) - .collect::>>()?; - match self._unify_types(args, &types) { - Ok(values) => { - let tokens = values - .iter() - .map(|arg| arg.try_into()) - .collect::>>()?; - let func = contract.function_from_selector(&func_abi.selector(), &tokens)?; - let is_view = func_abi.state_mutability == StateMutability::Pure - || func_abi.state_mutability == StateMutability::View; - match call.mode { - ContractCallMode::Default => { - if is_view { - call_result = self._execute_contract_call(func).await; - } else { - call_result = self - ._execute_contract_send(&call.addr, func, &call.options, env) - .await - } - } - ContractCallMode::Encode => { - let encoded = func.calldata(); - call_result = Ok(Value::Bytes(encoded[..].to_vec())); - } - ContractCallMode::Call => { - call_result = self._execute_contract_call(func).await - } - ContractCallMode::Send => { - call_result = self - ._execute_contract_send(&call.addr, func, &call.options, env) - .await - } - } - break; - } - Err(e) => call_result = Err(e), - } - } - call_result - } - - async fn _execute_contract_send( - &self, - addr: &Address, - func: CallBuilder, - opts: &CallOptions, - env: &Env, - ) -> Result - where - T: Transport + Clone, - P: Provider, - N: Network, - { - let data = func.calldata(); - let input = TransactionInput::new(data.clone()); - let from_ = env - .get_default_sender() - .ok_or(anyhow!("no wallet connected"))?; - let mut tx_req = TransactionRequest::default() - .with_from(from_) - .with_to(*addr) - .input(input); - if let Some(value) = opts.value.as_ref() { - let value = value.as_u256()?; - tx_req = tx_req.with_value(value); - } - - let provider = env.get_provider(); - let tx = provider.send_transaction(tx_req).await?; - Ok(Value::Transaction(*tx.tx_hash())) - } - - async fn _execute_contract_call( - &self, - func: CallBuilder, - ) -> Result - where - T: Transport + Clone, - P: Provider, - N: Network, - { - let result = func.call().await?; - let return_values = result - .into_iter() - .map(Value::try_from) - .collect::>>()?; - if return_values.len() == 1 { - Ok(return_values.into_iter().next().unwrap()) - } else { - Ok(Value::Tuple(return_values)) - } - } - - fn _unify_types(&self, args: &[Value], types: &[Type]) -> Result> { - if args.len() != types.len() { - bail!( - "function {} expects {} arguments, but got {}", - self, - types.len(), - args.len() - ); - } - let mut result = Vec::new(); - for (i, (arg, type_)) in args.iter().zip(types).enumerate() { - match type_.cast(arg) { - Ok(v) => result.push(v), - Err(e) => bail!( - "expected {} argument {} to be {}, but got {} ({})", - self, - i, - type_, - arg.get_type(), - e - ), - } - } - Ok(result) - } -} diff --git a/src/interpreter/functions/contract.rs b/src/interpreter/functions/contract.rs new file mode 100644 index 0000000..801016c --- /dev/null +++ b/src/interpreter/functions/contract.rs @@ -0,0 +1,230 @@ +use std::sync::Arc; + +use alloy::{ + contract::{CallBuilder, ContractInstance, Interface}, + json_abi::StateMutability, + network::{Network, TransactionBuilder}, + primitives::{keccak256, Address, FixedBytes}, + providers::Provider, + rpc::types::{TransactionInput, TransactionRequest}, + transports::Transport, +}; +use anyhow::{anyhow, bail, Result}; +use futures::{future::BoxFuture, FutureExt}; +use itertools::Itertools; + +use crate::interpreter::{types::HashableIndexMap, ContractInfo, Env, Type, Value}; + +use super::{Function, FunctionDef, FunctionParam}; + +#[derive(Debug, Clone, PartialEq, Hash, Eq)] +pub enum ContractCallMode { + Default, + Encode, + Call, + Send, +} + +impl TryFrom<&str> for ContractCallMode { + type Error = anyhow::Error; + + fn try_from(s: &str) -> Result { + match s { + "encode" => Ok(ContractCallMode::Encode), + "call" => Ok(ContractCallMode::Call), + "send" => Ok(ContractCallMode::Send), + _ => bail!("{} does not exist for contract call", s), + } + } +} + +#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)] +pub struct CallOptions { + value: Option>, +} + +impl std::fmt::Display for CallOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(v) = &self.value { + write!(f, "value: {}", v) + } else { + write!(f, "") + } + } +} + +impl TryFrom<&HashableIndexMap> for CallOptions { + type Error = anyhow::Error; + + fn try_from(value: &HashableIndexMap) -> std::result::Result { + let mut opts = CallOptions::default(); + for (k, v) in value.0.iter() { + match k.as_str() { + "value" => opts.value = Some(Box::new(v.clone())), + _ => bail!("unexpected key {}", k), + } + } + Ok(opts) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct ContractFunction { + func_name: String, + mode: ContractCallMode, +} + +impl ContractFunction { + pub fn arc(name: &str) -> Arc { + Arc::new(Self { + func_name: name.to_string(), + mode: ContractCallMode::Default, + }) + } + + pub fn with_mode(&self, mode: ContractCallMode) -> Self { + let mut new = self.clone(); + new.mode = mode; + new + } + + pub fn get_signature(&self, types_: &[Type]) -> String { + let mut selector = self.func_name.clone(); + selector.push('('); + let args_str = types_ + .iter() + .map(|t| t.canonical_string().expect("canonical string")) + .join(","); + selector.push_str(&args_str); + selector.push(')'); + selector + } + + pub fn get_selector(&self, types_: &[Type]) -> FixedBytes<4> { + let signature_hash = keccak256(self.get_signature(types_)); + FixedBytes::<4>::from_slice(&signature_hash[..4]) + } +} + +impl FunctionDef for ContractFunction { + fn name(&self) -> &str { + &self.func_name + } + + fn get_valid_args(&self, receiver: &Option) -> Vec> { + let (ContractInfo(_, abi), _) = receiver.clone().unwrap().as_contract().unwrap(); + let functions = abi.function(self.name()).cloned().unwrap_or(vec![]); + + functions + .into_iter() + .filter_map(|f| { + f.inputs + .into_iter() + .map(FunctionParam::try_from) + .collect::>>() + .ok() + }) + .collect() + } + + fn is_property(&self) -> bool { + false + } + + fn member_access(&self, receiver: &Option, member: &str) -> Option { + ContractCallMode::try_from(member) + .map(|m| Function::new(Arc::new(self.with_mode(m)), receiver.as_ref()).into()) + .ok() + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + let (ContractInfo(_, abi), addr) = values[0].as_contract().unwrap(); + let types_ = values[1..].iter().map(Value::get_type).collect::>(); + let selector = self.get_selector(&types_); + + async move { + let abi_func = abi + .functions() + .find(|f| f.selector() == selector) + .ok_or_else(|| anyhow!("function {} not found", self.get_signature(&types_)))?; + let interface = Interface::new(abi.clone()); + let contract = + ContractInstance::new(addr, env.get_provider().root().clone(), interface); + let call_options: CallOptions = options.try_into()?; + let tokens = values[1..] + .iter() + .map(|arg| arg.try_into()) + .collect::>>()?; + let func = contract.function_from_selector(&selector, &tokens)?; + let is_view = abi_func.state_mutability == StateMutability::Pure + || abi_func.state_mutability == StateMutability::View; + + if self.mode == ContractCallMode::Encode { + let encoded = func.calldata(); + Ok(Value::Bytes(encoded[..].to_vec())) + } else if self.mode == ContractCallMode::Call + || (self.mode == ContractCallMode::Default && is_view) + { + _execute_contract_call(func).await + } else { + _execute_contract_send(&addr, func, &call_options, env).await + } + } + .boxed() + } +} + +async fn _execute_contract_send( + addr: &Address, + func: CallBuilder, + opts: &CallOptions, + env: &Env, +) -> Result +where + T: Transport + Clone, + P: Provider, + N: Network, +{ + let data = func.calldata(); + let input = TransactionInput::new(data.clone()); + let from_ = env + .get_default_sender() + .ok_or(anyhow!("no wallet connected"))?; + let mut tx_req = TransactionRequest::default() + .with_from(from_) + .with_to(*addr) + .input(input); + if let Some(value) = opts.value.as_ref() { + let value = value.as_u256()?; + tx_req = tx_req.with_value(value); + } + + let provider = env.get_provider(); + let tx = provider.send_transaction(tx_req).await?; + Ok(Value::Transaction(*tx.tx_hash())) +} + +async fn _execute_contract_call( + func: CallBuilder, +) -> Result +where + T: Transport + Clone, + P: Provider, + N: Network, +{ + let result = func.call().await?; + let return_values = result + .into_iter() + .map(Value::try_from) + .collect::>>()?; + if return_values.len() == 1 { + Ok(return_values.into_iter().next().unwrap()) + } else { + Ok(Value::Tuple(return_values)) + } +} diff --git a/src/interpreter/functions/definition.rs b/src/interpreter/functions/definition.rs new file mode 100644 index 0000000..ecaede5 --- /dev/null +++ b/src/interpreter/functions/definition.rs @@ -0,0 +1,251 @@ +use std::sync::Arc; + +use crate::interpreter::{functions::FunctionParam, types::HashableIndexMap, Env, Value}; +use anyhow::{anyhow, Result}; +use futures::{future::BoxFuture, FutureExt}; + +pub trait FunctionDef: std::fmt::Debug + Send + Sync { + fn name(&self) -> &str; + + fn get_valid_args(&self, receiver: &Option) -> Vec>; + + fn is_property(&self) -> bool; + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result>; + + fn member_access(&self, _receiver: &Option, _member: &str) -> Option { + None + } +} + +#[derive(Debug)] +pub struct SyncProperty { + name: String, + f: fn(&Env, &Value) -> Result, +} + +impl SyncProperty { + pub fn arc(name: &str, f: fn(&Env, &Value) -> Result) -> Arc { + Arc::new(Self { + name: name.to_string(), + f, + }) + } +} + +impl FunctionDef for SyncProperty { + fn name(&self) -> &str { + &self.name + } + + fn get_valid_args(&self, _: &Option) -> Vec> { + vec![vec![]] + } + + fn is_property(&self) -> bool { + true + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { + let receiver = values.first().ok_or(anyhow!("no receiver"))?; + (self.f)(env, receiver) + } + .boxed() + } +} + +#[derive(Debug)] +pub struct AsyncProperty { + name: String, + f: for<'a> fn(&'a Env, &'a Value) -> BoxFuture<'a, Result>, +} + +impl AsyncProperty { + pub fn arc( + name: &str, + f: for<'a> fn(&'a Env, &'a Value) -> BoxFuture<'a, Result>, + ) -> Arc { + Arc::new(Self { + name: name.to_string(), + f, + }) + } +} + +impl FunctionDef for AsyncProperty { + fn name(&self) -> &str { + &self.name + } + + fn get_valid_args(&self, _: &Option) -> Vec> { + vec![vec![]] + } + + fn is_property(&self) -> bool { + true + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { + let receiver = values.first().ok_or(anyhow!("no receiver"))?; + (self.f)(env, receiver).await + } + .boxed() + } +} + +#[derive(Debug)] +pub struct SyncMethod { + name: String, + f: fn(&mut Env, &Value, &[Value]) -> Result, + valid_args: Vec>, +} + +impl SyncMethod { + pub fn arc( + name: &str, + f: fn(&mut Env, &Value, &[Value]) -> Result, + valid_args: Vec>, + ) -> Arc { + Arc::new(Self { + name: name.to_string(), + f, + valid_args, + }) + } +} + +impl FunctionDef for SyncMethod { + fn name(&self) -> &str { + &self.name + } + + fn get_valid_args(&self, _: &Option) -> Vec> { + self.valid_args.clone() + } + + fn is_property(&self) -> bool { + false + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { + let receiver = values.first().ok_or(anyhow!("no receiver"))?; + (self.f)(env, receiver, &values[1..]) + } + .boxed() + } +} + +#[derive(Debug)] +pub struct SyncFunction { + name: String, + f: fn(&Env, &[Value]) -> Result, + valid_args: Vec>, +} + +impl SyncFunction { + pub fn arc( + name: &str, + f: fn(&Env, &[Value]) -> Result, + valid_args: Vec>, + ) -> Arc { + Arc::new(Self { + name: name.to_string(), + f, + valid_args, + }) + } +} + +impl FunctionDef for SyncFunction { + fn name(&self) -> &str { + &self.name + } + + fn get_valid_args(&self, _: &Option) -> Vec> { + self.valid_args.clone() + } + + fn is_property(&self) -> bool { + false + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { (self.f)(env, values) }.boxed() + } +} + +#[derive(Debug)] +pub struct AsyncMethod { + name: String, + f: for<'a> fn(&'a mut Env, &'a Value, &'a [Value]) -> BoxFuture<'a, Result>, + valid_args: Vec>, +} + +impl AsyncMethod { + pub fn arc( + name: &str, + f: for<'a> fn(&'a mut Env, &'a Value, &'a [Value]) -> BoxFuture<'a, Result>, + valid_args: Vec>, + ) -> Arc { + Arc::new(Self { + name: name.to_string(), + f, + valid_args, + }) + } +} + +impl FunctionDef for AsyncMethod { + fn name(&self) -> &str { + &self.name + } + + fn get_valid_args(&self, _: &Option) -> Vec> { + self.valid_args.clone() + } + + fn is_property(&self) -> bool { + false + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { + let receiver = values.first().ok_or(anyhow!("no receiver"))?; + (self.f)(env, receiver, &values[1..]).await + } + .boxed() + } +} diff --git a/src/interpreter/functions/function.rs b/src/interpreter/functions/function.rs new file mode 100644 index 0000000..04c21d1 --- /dev/null +++ b/src/interpreter/functions/function.rs @@ -0,0 +1,165 @@ +use crate::interpreter::{types::HashableIndexMap, utils::join_with_final, Env, Value}; +use anyhow::{anyhow, bail, Result}; +use itertools::Itertools; +use std::{fmt, sync::Arc}; + +use super::{definition::FunctionDef, FunctionParam}; + +#[derive(Debug, Clone)] +pub struct Function { + def: Arc, + receiver: Option, + options: HashableIndexMap, +} + +impl std::hash::Hash for Function { + fn hash(&self, state: &mut H) { + self.receiver.hash(state); + self.def.name().hash(state); + let args = self.def.get_valid_args(&self.receiver); + args.hash(state) + } +} + +impl std::cmp::PartialEq for Function { + fn eq(&self, other: &Self) -> bool { + if self.receiver != other.receiver || self.def.name() != other.def.name() { + return false; + } + let args = self.def.get_valid_args(&self.receiver); + let other_args = other.def.get_valid_args(&other.receiver); + args == other_args + } +} + +impl std::cmp::Eq for Function {} + +impl fmt::Display for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let variants = self.get_variants(); + for (i, variant) in variants.iter().enumerate() { + if i > 0 { + writeln!(f)?; + } + if let Some(receiver) = &self.receiver { + write!(f, "{}.", receiver)?; + } + write!(f, "{}", variant)?; + } + Ok(()) + } +} + +impl Function { + pub fn new(def: Arc, receiver: Option<&Value>) -> Self { + Function { + def: def.clone(), + receiver: receiver.cloned(), + options: HashableIndexMap::default(), + } + } + + pub fn member_access(&self, member: &str) -> Result { + self.def + .member_access(&self.receiver, member) + .ok_or(anyhow!("no member {} for {}", member, self)) + } + + pub fn with_opts(self, opts: HashableIndexMap) -> Self { + let mut new = self; + new.options = opts; + new + } + + pub fn get_valid_args_lengths(&self) -> Vec { + let args = self.def.get_valid_args(&self.receiver); + let valid_lengths = args.iter().map(|args| args.len()); + valid_lengths.dedup().sorted().collect() + } + + pub fn get_variants(&self) -> Vec { + self.def + .get_valid_args(&self.receiver) + .iter() + .map(|args| { + let args = args + .iter() + .map(|arg| arg.to_string()) + .collect::>() + .join(", "); + format!("{}({})", self.def.name(), args) + }) + .collect() + } + + pub fn method(def: Arc, receiver: &Value) -> Self { + Self::new(def, Some(receiver)) + } + + pub fn is_property(&self) -> bool { + self.def.is_property() + } + + pub async fn execute(&self, env: &mut Env, args: &[Value]) -> Result { + env.push_scope(); + let result = self.execute_in_current_scope(env, args).await; + env.pop_scope(); + result + } + + pub async fn execute_in_current_scope(&self, env: &mut Env, args: &[Value]) -> Result { + let mut unified_args = self.get_unified_args(args)?; + if let Some(receiver) = &self.receiver { + unified_args.insert(0, receiver.clone()); + } + self.def.execute(env, &unified_args, &self.options).await + } + + fn get_unified_args(&self, args: &[Value]) -> Result> { + let valid_args_lengths = self.get_valid_args_lengths(); + + // skip validation if no valid args are specified + if valid_args_lengths.is_empty() { + return Ok(args.to_vec()); + } + + if !valid_args_lengths.contains(&args.len()) { + bail!( + "function {} expects {} arguments, but got {}", + self, + join_with_final(", ", " or ", valid_args_lengths), + args.len() + ); + } + + let valid_args = self.def.get_valid_args(&self.receiver); + let potential_types = valid_args.iter().filter(|a| a.len() == args.len()); + + for (i, arg_types) in potential_types.enumerate() { + let res = self._unify_types(args, arg_types.as_slice()); + if res.is_ok() || i == valid_args_lengths.len() - 1 { + return res; + } + } + + unreachable!() + } + + fn _unify_types(&self, args: &[Value], types: &[FunctionParam]) -> Result> { + let mut result = vec![]; + for (i, (arg, param)) in args.iter().zip(types).enumerate() { + match param.get_type().cast(arg) { + Ok(v) => result.push(v), + Err(e) => bail!( + "expected {} argument {} to be {}, but got {} ({})", + self, + i, + param.get_type(), + arg.get_type(), + e + ), + } + } + Ok(result) + } +} diff --git a/src/interpreter/functions/mod.rs b/src/interpreter/functions/mod.rs new file mode 100644 index 0000000..b021fd7 --- /dev/null +++ b/src/interpreter/functions/mod.rs @@ -0,0 +1,13 @@ +mod contract; +mod definition; +mod function; +mod param; +mod user_defined; + +pub use contract::ContractFunction; +pub use definition::{ + AsyncMethod, AsyncProperty, FunctionDef, SyncFunction, SyncMethod, SyncProperty, +}; +pub use function::Function; +pub use param::FunctionParam; +pub use user_defined::UserDefinedFunction; diff --git a/src/interpreter/functions/param.rs b/src/interpreter/functions/param.rs new file mode 100644 index 0000000..d8137fa --- /dev/null +++ b/src/interpreter/functions/param.rs @@ -0,0 +1,63 @@ +use alloy::{dyn_abi::Specifier, json_abi::Param}; +use anyhow::{bail, Result}; +use solang_parser::pt::{Expression, Identifier, Parameter}; +use std::fmt; + +use crate::interpreter::Type; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FunctionParam { + name: String, + type_: Type, +} + +impl FunctionParam { + pub fn new(name: &str, type_: Type) -> Self { + Self { + name: name.to_string(), + type_, + } + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn get_type(&self) -> &Type { + &self.type_ + } +} + +impl fmt::Display for FunctionParam { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.type_, self.name) + } +} + +impl TryFrom for FunctionParam { + type Error = anyhow::Error; + + fn try_from(param: Param) -> std::result::Result { + let name = param.name.clone(); + let type_ = param.resolve()?.into(); + Ok(FunctionParam { name, type_ }) + } +} + +impl TryFrom for FunctionParam { + type Error = anyhow::Error; + + fn try_from(p: Parameter) -> Result { + match (p.name, p.ty) { + (Some(Identifier { name, .. }), Expression::Type(_, t)) => { + let type_ = t.try_into()?; + Ok(FunctionParam { name, type_ }) + } + (None, Expression::Variable(Identifier { name, .. })) => Ok(FunctionParam { + name, + type_: Type::Any, + }), + _ => bail!("require param name or type and name"), + } + } +} diff --git a/src/interpreter/functions/user_defined.rs b/src/interpreter/functions/user_defined.rs new file mode 100644 index 0000000..c24c394 --- /dev/null +++ b/src/interpreter/functions/user_defined.rs @@ -0,0 +1,77 @@ +use std::sync::Arc; + +use crate::interpreter::{evaluate_statement, types::HashableIndexMap, Env, Value}; + +use super::{Function, FunctionDef, FunctionParam}; +use anyhow::{anyhow, Result}; +use futures::{future::BoxFuture, FutureExt}; +use solang_parser::pt::Statement; + +#[derive(Debug)] +pub struct UserDefinedFunction { + func_name: String, + params: Vec, + body: Statement, +} + +impl From for Value { + fn from(f: UserDefinedFunction) -> Self { + Value::Func(Box::new(Function::new(Arc::new(f), None))) + } +} + +impl FunctionDef for UserDefinedFunction { + fn name(&self) -> &str { + self.func_name.as_str() + } + + fn get_valid_args(&self, _receiver: &Option) -> Vec> { + vec![self.params.clone()] + } + + fn is_property(&self) -> bool { + false + } + + fn execute<'a>( + &'a self, + env: &'a mut Env, + values: &'a [Value], + _options: &'a HashableIndexMap, + ) -> BoxFuture<'a, Result> { + async move { + for (param, arg) in self.params.iter().zip(values.iter()) { + env.set_var(param.get_name(), arg.clone()); + } + evaluate_statement(env, Box::new(self.body.clone())) + .await + .map(|v| v.value().cloned().unwrap_or(Value::Null)) + } + .boxed() + } +} + +impl TryFrom for UserDefinedFunction { + type Error = anyhow::Error; + + fn try_from(f: solang_parser::pt::FunctionDefinition) -> Result { + let name = f.name.clone().ok_or(anyhow!("require function name"))?.name; + let body = f.body.clone().ok_or(anyhow!("missing function body"))?; + let params = f + .params + .iter() + .map(|(_, p)| { + p.clone() + .ok_or(anyhow!("require param")) + .and_then(FunctionParam::try_from) + }) + .collect::>>()?; + + let func = UserDefinedFunction { + func_name: name, + params, + body, + }; + Ok(func) + } +} diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index d2a8c4e..6598eac 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -12,8 +12,8 @@ use solang_parser::pt::{ContractPart, Expression, Statement}; use crate::loaders::types::Project; use super::assignment::Lhs; -use super::builtin_functions::BuiltinFunction; -use super::functions::{Function, UserDefinedFunction}; +use super::builtins; +use super::functions::{FunctionDef, UserDefinedFunction}; use super::parsing::ParsedCode; use super::types::{HashableIndexMap, Type}; use super::{env::Env, parsing, value::Value}; @@ -48,25 +48,18 @@ impl StatementResult { _ => None, } } + + pub fn as_value(&self) -> Result<&Value> { + self.value().ok_or(anyhow!("expected value, got {}", self)) + } } unsafe impl std::marker::Send for StatementResult {} pub fn load_builtins(env: &mut Env) { - env.set_var("repl", Value::TypeObject(Type::Repl)); - env.set_var("console", Value::TypeObject(Type::Console)); - env.set_var("block", Value::TypeObject(Type::Block)); - env.set_var("Transaction", Value::TypeObject(Type::Transaction)); - env.set_var("abi", Value::TypeObject(Type::Abi)); - - for name in BuiltinFunction::functions() { - env.set_var( - &name, - Value::Func(Function::Builtin( - BuiltinFunction::from_name(&name).unwrap(), - )), - ); - } + builtins::VALUES.iter().for_each(|(name, value)| { + env.set_var(name, value.clone()); + }); } pub fn load_project(env: &mut Env, project: &Project) -> Result<()> { @@ -87,8 +80,8 @@ pub async fn evaluate_setup(env: &mut Env, code: &str) -> Result<()> { let def = parsing::parse_contract(code)?; evaluate_contract_parts(env, &def.parts).await?; let setup = env.get_var(SETUP_FUNCTION_NAME).cloned(); - if let Some(Value::Func(func @ Function::UserDefined(_))) = setup { - func.execute_in_current_scope(&[], env).await?; + if let Some(Value::Func(func)) = setup { + func.execute_in_current_scope(env, &[]).await?; env.delete_var(SETUP_FUNCTION_NAME) } @@ -140,7 +133,8 @@ pub async fn evaluate_contract_part( match part { ContractPart::FunctionDefinition(def) => { let func = UserDefinedFunction::try_from(def.as_ref().clone())?; - env.set_var(&func.name, Value::Func(Function::UserDefined(func.clone()))); + let name = func.name().to_string(); + env.set_var(&name, func.into()); } ContractPart::VariableDefinition(def) => { env.init_variable(&def.name, &def.ty, &def.initializer) @@ -387,10 +381,12 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ }, Expression::Equal(_, lhs, rhs) => { - _eval_comparison(env, lhs, rhs, |o| o == Ordering::Equal).await + let eq = _equals(env, lhs.clone(), rhs.clone()).await?; + Ok(Value::Bool(eq)) } Expression::NotEqual(_, lhs, rhs) => { - _eval_comparison(env, lhs, rhs, |o| o == Ordering::Equal).await + let eq = _equals(env, lhs.clone(), rhs.clone()).await?; + Ok(Value::Bool(!eq)) } Expression::Less(_, lhs, rhs) => { _eval_comparison(env, lhs, rhs, |o| o == Ordering::Less).await @@ -437,11 +433,9 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ Expression::MemberAccess(_, receiver_expr, method) => { let receiver = evaluate_expression(env, receiver_expr).await?; - let function = Function::with_receiver(&receiver, &method.name)?; - if function.is_property() { - Ok(function.execute(&[], env).await?) - } else { - Ok(Value::Func(function)) + match receiver.member_access(&method.name) { + Result::Ok(Value::Func(f)) if f.is_property() => f.execute(env, &[]).await, + v => v, } } @@ -463,13 +457,17 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ for expr in exprs.iter() { values.push(evaluate_expression(env, Box::new(expr.clone())).await?); } - Ok(Value::Array(values)) + let type_ = values + .first() + .map(|v| v.get_type().clone()) + .unwrap_or(Type::Any); + Ok(Value::Array(values, Box::new(type_))) } Expression::ArraySubscript(_, expr, subscript_opt) => { let lhs = evaluate_expression(env, expr).await?; match lhs { - Value::Tuple(values) | Value::Array(values) => { + Value::Tuple(values) | Value::Array(values, _) => { let subscript = subscript_opt .ok_or(anyhow!("tuples and arrays do not support empty subscript"))?; let index = evaluate_expression(env, subscript).await?.as_usize()?; @@ -500,8 +498,8 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ } Expression::ArraySlice(_, arr_expr, start_expr, end_expr) => { - let values = match evaluate_expression(env, arr_expr).await? { - Value::Array(v) => v, + let (values, type_) = match evaluate_expression(env, arr_expr).await? { + Value::Array(v, t) => (v, t), v => bail!("invalid type for slice, expected tuple, got {}", v), }; let start = match start_expr { @@ -515,7 +513,7 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ if end > values.len() { bail!("end index out of bounds"); } - Ok(Value::Array(values[start..end].to_vec())) + Ok(Value::Array(values[start..end].to_vec(), type_.clone())) } Expression::Add(_, lhs, rhs) => _eval_binop(env, lhs, rhs, Value::add).await, @@ -570,7 +568,7 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ args.push(evaluate_expression(env, Box::new(arg.clone())).await?); } match evaluate_expression(env, func_expr).await? { - Value::Func(f) => f.execute(&args, env).await, + Value::Func(f) => f.execute(env, &args).await, Value::TypeObject(type_) => { if let [arg] = &args[..] { type_.cast(arg) @@ -585,7 +583,10 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ Expression::FunctionCallBlock(_, func_expr, stmt) => { let res = evaluate_statement(env, stmt).await?; match evaluate_expression(env, func_expr).await? { - Value::Func(f) => Ok(Value::Func(f.with_opts(res.try_into()?))), + Value::Func(f) => { + let opts = res.as_value()?.as_record()?.clone(); + Ok(f.with_opts(opts).into()) + } _ => bail!("expected function"), } } @@ -599,6 +600,12 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ .boxed() } +async fn _equals(env: &mut Env, lexpr: Box, rexpr: Box) -> Result { + let lhs = evaluate_expression(env, lexpr).await?; + let rhs = evaluate_expression(env, rexpr).await?; + Ok(lhs == rhs) +} + async fn _eval_comparison( env: &mut Env, lexpr: Box, diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index d6debe1..297eda8 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,18 +1,16 @@ mod assignment; -mod block_functions; -mod builtin_functions; +mod builtins; mod config; -mod directive; mod env; mod functions; #[allow(clippy::module_inception)] mod interpreter; mod parsing; mod types; +mod utils; mod value; pub use config::Config; -pub use directive::Directive; pub use env::Env; pub use interpreter::*; pub use types::{ContractInfo, Type}; diff --git a/src/interpreter/types.rs b/src/interpreter/types.rs index 486d527..68a75a9 100644 --- a/src/interpreter/types.rs +++ b/src/interpreter/types.rs @@ -12,9 +12,9 @@ use itertools::Itertools; use solang_parser::pt as parser; use super::{ - block_functions::BlockFunction, - functions::{ContractCall, Function}, - Directive, Value, + builtins::{INSTANCE_METHODS, STATIC_METHODS}, + functions::{ContractFunction, Function}, + Value, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -23,6 +23,16 @@ where K: Eq + std::hash::Hash, V: Eq; +impl std::default::Default for HashableIndexMap +where + K: Eq + std::hash::Hash, + V: Eq, +{ + fn default() -> Self { + Self(IndexMap::default()) + } +} + impl std::hash::Hash for HashableIndexMap where K: std::hash::Hash + Eq, @@ -47,16 +57,15 @@ where pub struct ContractInfo(pub String, pub JsonAbi); impl ContractInfo { - pub fn create_call(&self, name: &str, addr: Address) -> Result { + pub fn make_function(&self, name: &str, addr: Address) -> Result { let _func = self .1 .function(name) .ok_or_else(|| anyhow::anyhow!("function {} not found in contract {}", name, self.0))?; - Ok(Function::ContractCall(ContractCall::new( - self.clone(), - addr, - name.to_string(), - ))) + Ok(Function::new( + ContractFunction::arc(name), + Some(&Value::Contract(self.clone(), addr)), + )) } } @@ -80,12 +89,19 @@ impl Receipt { "status" => Value::Bool(self.status), "gas_used" => Value::Uint(U256::from(self.gas_used), 256), "effective_gas_price" => Value::Uint(U256::from(self.effective_gas_price), 256), - "logs" => Value::Array(self.logs.iter().map(|log| log.into()).collect()), + "logs" => Value::Array( + self.logs.iter().map(|log| log.into()).collect(), + Box::new(Type::FixBytes(32)), + ), _ => bail!("receipt has no field {}", field), }; Ok(result) } + pub fn contains_key(&self, key: &str) -> bool { + Self::keys().contains(&key.to_string()) + } + pub fn keys() -> Vec { let keys = [ "tx_hash", @@ -125,8 +141,36 @@ impl Display for Receipt { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum NonParametricType { + Any, + Null, + Address, + Bool, + Int, + Uint, + FixBytes, + Bytes, + String, + Array, + FixedArray, + NamedTuple, + Tuple, + Mapping, + Contract, + Transaction, + TransactionReceipt, + Function, + Repl, + Block, + Console, + Abi, + Type, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Type { + Any, Null, Address, Bool, @@ -154,6 +198,7 @@ pub enum Type { impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Type::Any => write!(f, "any"), Type::Null => write!(f, "null"), Type::Address => write!(f, "address"), Type::Bool => write!(f, "bool"), @@ -176,8 +221,8 @@ impl Display for Type { Type::Contract(ContractInfo(name, _)) => write!(f, "{}", name), Type::Function => write!(f, "function"), - Type::Transaction => write!(f, "transaction"), - Type::TransactionReceipt => write!(f, "transactionReceipt"), + Type::Transaction => write!(f, "Transaction"), + Type::TransactionReceipt => write!(f, "TransactionReceipt"), Type::Repl => write!(f, "repl"), Type::Block => write!(f, "block"), @@ -188,6 +233,42 @@ impl Display for Type { } } +impl> From for NonParametricType { + fn from(value: T) -> Self { + match value.as_ref() { + Type::Any => NonParametricType::Any, + Type::Null => NonParametricType::Null, + Type::Address => NonParametricType::Address, + Type::Bool => NonParametricType::Bool, + Type::Int(_) => NonParametricType::Int, + Type::Uint(_) => NonParametricType::Uint, + Type::FixBytes(_) => NonParametricType::FixBytes, + Type::Bytes => NonParametricType::Bytes, + Type::String => NonParametricType::String, + Type::Array(_) => NonParametricType::Array, + Type::FixedArray(..) => NonParametricType::FixedArray, + Type::NamedTuple(..) => NonParametricType::NamedTuple, + Type::Tuple(_) => NonParametricType::Tuple, + Type::Mapping(..) => NonParametricType::Mapping, + Type::Contract(..) => NonParametricType::Contract, + Type::Function => NonParametricType::Function, + Type::Transaction => NonParametricType::Transaction, + Type::TransactionReceipt => NonParametricType::TransactionReceipt, + Type::Repl => NonParametricType::Repl, + Type::Block => NonParametricType::Block, + Type::Console => NonParametricType::Console, + Type::Abi => NonParametricType::Abi, + Type::Type(_) => NonParametricType::Type, + } + } +} + +impl AsRef for Type { + fn as_ref(&self) -> &Type { + self + } +} + impl From for Type { fn from(type_: DynSolType) -> Self { match type_ { @@ -292,6 +373,15 @@ impl TryFrom for DynSolType { } } +fn canonical_string_for_tuple(types: &[Type]) -> Result { + let items = types + .iter() + .map(|t| t.canonical_string()) + .collect::>>()? + .join(","); + Ok(format!("({})", items)) +} + impl Type { pub fn default_value(&self) -> Result { let value = match self { @@ -303,8 +393,8 @@ impl Type { Type::FixBytes(size) => Value::FixBytes(B256::default(), *size), Type::Bytes => Value::Bytes(vec![]), Type::String => Value::Str("".to_string()), - Type::Array(_) => Value::Array(vec![]), - Type::FixedArray(t, size) => Value::Array(vec![t.default_value()?; *size]), + Type::Array(t) => Value::Array(vec![], t.clone()), + Type::FixedArray(t, size) => Value::Array(vec![t.default_value()?; *size], t.clone()), Type::NamedTuple(_, fields) => Value::NamedTuple( "".to_string(), fields @@ -313,7 +403,7 @@ impl Type { .map(|(k, v)| v.default_value().map(|v_| (k.clone(), v_))) .collect::>>()?, ), - Type::Tuple(types) => Value::Array( + Type::Tuple(types) => Value::Tuple( types .iter() .map(|t| t.default_value()) @@ -327,6 +417,27 @@ impl Type { Ok(value) } + pub fn canonical_string(&self) -> Result { + let result = match self { + Type::Address => "address".to_string(), + Type::Bool => "bool".to_string(), + Type::Int(size) => format!("int{}", size), + Type::Uint(size) => format!("uint{}", size), + Type::FixBytes(size) => format!("bytes{}", size), + Type::Bytes => "bytes".to_string(), + Type::String => "string".to_string(), + Type::Array(t) => format!("{}[]", t.canonical_string()?), + Type::FixedArray(t, size) => format!("{}[{}]", t.canonical_string()?, size), + Type::NamedTuple(_, fields) => { + let types = fields.0.values().cloned().collect_vec(); + canonical_string_for_tuple(&types)? + } + Type::Tuple(types) => canonical_string_for_tuple(types.as_slice())?, + _ => bail!("cannot get canonical string for type {}", self), + }; + Ok(result) + } + pub fn is_int(&self) -> bool { matches!(self, Type::Int(_) | Type::Uint(_)) } @@ -349,11 +460,13 @@ impl Type { "uint256".to_string(), "bytes".to_string(), "string".to_string(), + "mapping".to_string(), ] } pub fn cast(&self, value: &Value) -> Result { match (self, value) { + (Type::Any, value) => Ok(value.clone()), (type_, value) if type_ == &value.get_type() => Ok(value.clone()), (Type::Contract(info), Value::Addr(addr)) => Ok(Value::Contract(info.clone(), *addr)), (Type::Address, Value::Contract(_, addr)) => Ok(Value::Addr(*addr)), @@ -394,17 +507,17 @@ impl Type { HashableIndexMap(new_values), )) } - (Type::Array(t), Value::Array(v)) => v + (Type::Array(t), Value::Array(v, _)) => v .iter() .map(|value| t.cast(value)) .collect::>>() - .map(Value::Array), + .map(|items| Value::Array(items, t.clone())), (Type::Tuple(types), Value::Tuple(v)) => v .iter() .zip(types.iter()) .map(|(value, t)| t.cast(value)) .collect::>>() - .map(Value::Array), + .map(Value::Tuple), _ => bail!("cannot cast {} to {}", value.get_type(), self), } } @@ -415,44 +528,22 @@ impl Type { abi.functions.keys().map(|s| s.to_string()).collect() } Type::NamedTuple(_, fields) => fields.0.keys().map(|s| s.to_string()).collect(), - Type::Uint(_) | Type::Int(_) => { - vec!["format".to_string()] - } - Type::Transaction => vec!["getReceipt".to_string()], Type::TransactionReceipt => Receipt::keys(), - Type::Array(_) => vec![ - "concat".to_string(), - "length".to_string(), - "map".to_string(), - ], - Type::String => vec!["concat".to_string(), "length".to_string()], - - Type::Bytes => vec!["concat".to_string(), "length".to_string()], - - Type::Mapping(_, _) => vec!["keys".to_string()], - - Type::Type(t) => match *t.clone() { - Type::Contract(_) => vec!["decode".to_string()], - Type::Abi => vec![ - "encode".to_string(), - "decode".to_string(), - "encodePacked".to_string(), - ], - Type::Console => vec!["log".to_string()], - Type::Block => BlockFunction::all(), - Type::Repl => Directive::all() - .into_iter() - .map(|s| s.to_string()) - .collect(), - Type::Uint(_) | Type::Int(_) => vec!["max".to_string(), "min".to_string()], - _ => vec![], - }, - _ => vec![], + Type::Type(type_) => STATIC_METHODS + .get(&type_.into()) + .map_or(vec![], |m| m.keys().cloned().collect()), + _ => INSTANCE_METHODS + .get(&self.into()) + .map_or(vec![], |m| m.keys().cloned().collect()), } } pub fn max(&self) -> Result { - let res = match self { + let inner_type = match self { + Type::Type(t) => t.as_ref(), + _ => self, + }; + let res = match inner_type { Type::Uint(256) => Value::Uint(U256::MAX, 256), Type::Uint(size) => { Value::Uint((U256::from(1) << U256::from(*size)) - U256::from(1), 256) @@ -467,7 +558,11 @@ impl Type { } pub fn min(&self) -> Result { - match self { + let inner_type = match self { + Type::Type(t) => t.as_ref(), + _ => self, + }; + match inner_type { Type::Uint(_) => Ok(0u64.into()), Type::Int(256) => Ok(Value::Int(I256::MIN, 256)), Type::Int(size) => { diff --git a/src/interpreter/utils.rs b/src/interpreter/utils.rs new file mode 100644 index 0000000..9d0dbc7 --- /dev/null +++ b/src/interpreter/utils.rs @@ -0,0 +1,19 @@ +pub fn join_with_final(separator: &str, final_separator: &str, strings: Vec) -> String +where + T: std::string::ToString, +{ + if strings.is_empty() { + return "".to_string(); + } + if strings.len() == 1 { + return strings[0].to_string(); + } + let mut result = strings[0].to_string(); + for s in strings[1..strings.len() - 1].iter() { + result.push_str(separator); + result.push_str(&s.to_string()); + } + result.push_str(final_separator); + result.push_str(&strings[strings.len() - 1].to_string()); + result +} diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 425115d..e5d6380 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -3,7 +3,7 @@ use alloy::{ hex, primitives::{Address, B256, I256, U256}, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use indexmap::IndexMap; use itertools::Itertools; use std::{ @@ -12,6 +12,7 @@ use std::{ }; use super::{ + builtins::{INSTANCE_METHODS, STATIC_METHODS, TYPE_METHODS}, functions::Function, types::{ContractInfo, HashableIndexMap, Receipt, Type}, }; @@ -29,12 +30,12 @@ pub enum Value { Contract(ContractInfo, Address), Tuple(Vec), NamedTuple(String, HashableIndexMap), - Array(Vec), + Array(Vec, Box), Mapping(HashableIndexMap, Box, Box), TypeObject(Type), Transaction(B256), TransactionReceipt(Receipt), - Func(Function), + Func(Box), } fn _values_to_string(values: &[Value]) -> String { @@ -76,7 +77,7 @@ impl Display for Value { write!(f, "{} {{ {} }}", name, _format_struct_fields(&v.0)) } Value::Tuple(v) => write!(f, "({})", _values_to_string(v)), - Value::Array(v) => write!(f, "[{}]", _values_to_string(v)), + Value::Array(v, _) => write!(f, "[{}]", _values_to_string(v)), Value::Mapping(v, kt, vt) => { let values = v.0.iter().map(|(k, v)| format!("{}: {}", k, v)).join(", "); write!(f, "mapping({} => {}) {{ {} }}", kt, vt, values) @@ -120,7 +121,7 @@ impl TryFrom<&Value> for alloy::dyn_abi::DynSolValue { } } Value::Tuple(vs) => DynSolValue::Tuple(_values_to_dyn_sol_values(vs)?), - Value::Array(vs) => DynSolValue::Array(_values_to_dyn_sol_values(vs)?), + Value::Array(vs, _) => DynSolValue::Array(_values_to_dyn_sol_values(vs)?), Value::Mapping(_, _, _) => bail!("cannot convert mapping to Solidity type"), Value::Null => bail!("cannot convert null to Solidity type"), Value::TransactionReceipt(_) => bail!("cannot convert receipt to Solidity type"), @@ -142,6 +143,7 @@ impl From for Value { .iter() .map(|t| Value::FixBytes(*t, 32)) .collect(), + Box::new(Type::FixBytes(32)), ), ); fields.insert("data".to_string(), Value::Bytes(log.data().data.to_vec())); @@ -163,7 +165,10 @@ impl TryFrom for Value { DynSolValue::FixedBytes(w, s) => Ok(Value::FixBytes(w, s)), DynSolValue::Bytes(v) => Ok(Value::Bytes(v)), DynSolValue::Tuple(vs) => _dyn_sol_values_to_values(vs).map(Value::Tuple), - DynSolValue::Array(vs) => _dyn_sol_values_to_values(vs).map(Value::Array), + DynSolValue::Array(vs) => _dyn_sol_values_to_values(vs).map(|xs| { + let type_ = xs.first().map(Value::get_type).unwrap_or(Type::Any); + Value::Array(xs, Box::new(type_)) + }), DynSolValue::CustomStruct { name, prop_names, @@ -179,7 +184,7 @@ impl TryFrom for Value { HashableIndexMap(IndexMap::from_iter(vs)), )) } - v => Err(anyhow::anyhow!("{:?} not supported", v)), + v => Err(anyhow!("{:?} not supported", v)), } } } @@ -196,6 +201,12 @@ impl From for Value { } } +impl From for Value { + fn from(n: usize) -> Self { + Value::Uint(U256::from(n), 256) + } +} + impl From for Value { fn from(n: u128) -> Self { Value::Uint(U256::from(n), 256) @@ -208,6 +219,12 @@ impl From<&str> for Value { } } +impl From for Value { + fn from(f: Function) -> Self { + Value::Func(Box::new(f)) + } +} + impl From> for Value { fn from(bytes: alloy::primitives::FixedBytes) -> Self { Value::FixBytes(B256::from_slice(&bytes[..]), N) @@ -223,6 +240,33 @@ where } } +impl> From<(T,)> for Value +where + T: Into, +{ + fn from(t: (T,)) -> Self { + Value::Tuple(vec![t.0.into()]) + } +} + +impl, U: Into> From<(T, U)> for Value +where + T: Into, +{ + fn from(t: (T, U)) -> Self { + Value::Tuple(vec![t.0.into(), t.1.into()]) + } +} + +impl, U: Into, V: Into> From<(T, U, V)> for Value +where + T: Into, +{ + fn from(t: (T, U, V)) -> Self { + Value::Tuple(vec![t.0.into(), t.1.into(), t.2.into()]) + } +} + impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { @@ -235,7 +279,7 @@ impl PartialOrd for Value { (Value::Addr(a), Value::Addr(b)) => a.partial_cmp(b), (Value::FixBytes(a, _), Value::FixBytes(b, _)) => a.partial_cmp(b), (Value::Tuple(a), Value::Tuple(b)) => a.partial_cmp(b), - (Value::Array(a), Value::Array(b)) => a.partial_cmp(b), + (Value::Array(a, _), Value::Array(b, _)) => a.partial_cmp(b), (Value::Contract(_, a), Value::Contract(_, b)) => a.partial_cmp(b), _ => None, } @@ -259,10 +303,7 @@ impl Value { .collect(), ), Value::Tuple(vs) => Type::Tuple(vs.iter().map(Value::get_type).collect()), - Value::Array(vs) => { - let t = vs.iter().map(Value::get_type).next().unwrap_or(Type::Bool); - Type::Array(Box::new(t)) - } + Value::Array(_, t) => Type::Array(t.clone()), Value::Mapping(_, kt, vt) => Type::Mapping(kt.clone(), vt.clone()), Value::Contract(c, _) => Type::Contract(c.clone()), Value::Null => Type::Null, @@ -277,7 +318,7 @@ impl Value { #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> Result { let len = match self { - Value::Array(items) => items.len(), + Value::Array(items, _) => items.len(), Value::Tuple(items) => items.len(), Value::Bytes(b) => b.len(), Value::Str(s) => s.len(), @@ -288,12 +329,12 @@ impl Value { pub fn set_index(&mut self, index: &Value, value: Value) -> Result<()> { match self { - Value::Array(items) => { + Value::Array(items, t) => { let int_index = index.as_usize()?; if int_index >= items.len() { bail!("index out of bounds") } - items[int_index] = value; + items[int_index] = t.cast(&value)?; Ok(()) } Value::Mapping(v, kt, vt) => { @@ -307,9 +348,7 @@ impl Value { pub fn is_builtin(&self) -> bool { matches!( self, - Value::TypeObject(Type::Console) - | Value::TypeObject(Type::Repl) - | Value::Func(Function::Builtin(_)) + Value::TypeObject(Type::Console) | Value::TypeObject(Type::Repl) ) } @@ -320,6 +359,13 @@ impl Value { } } + pub fn as_contract(&self) -> Result<(ContractInfo, Address)> { + match self { + Value::Contract(info, addr) => Ok((info.clone(), *addr)), + _ => bail!("cannot convert {} to contract", self.get_type()), + } + } + pub fn as_string(&self) -> Result { match self { Value::Str(str) => Ok(str.clone()), @@ -358,20 +404,27 @@ impl Value { } } + pub fn as_record(&self) -> Result<&HashableIndexMap> { + match self { + Value::NamedTuple(_, map) => Ok(map), + _ => bail!("cannot convert {} to map", self.get_type()), + } + } + pub fn get_field(&self, field: &str) -> Result { match self { Value::NamedTuple(_, fields) => fields .0 .get(field) .cloned() - .ok_or_else(|| anyhow::anyhow!("field {} not found", field)), + .ok_or(anyhow!("field {} not found", field)), _ => bail!("{} is not a struct", self.get_type()), } } pub fn get_items(&self) -> Result> { match self { - Value::Array(items) => Ok(items.clone()), + Value::Array(items, _) => Ok(items.clone()), Value::Tuple(items) => Ok(items.clone()), Value::NamedTuple(_, items) => Ok(items.0.values().cloned().collect()), _ => bail!("{} is not iterable", self.get_type()), @@ -391,6 +444,35 @@ impl Value { } Ok(self) } + + pub fn member_access(&self, member: &str) -> Result { + match self { + Value::NamedTuple(_, kv) if kv.0.contains_key(member) => { + Ok(kv.0.get(member).unwrap().clone()) + } + Value::TransactionReceipt(r) if r.contains_key(member) => r.get(member), + Value::Contract(c, addr) => c.make_function(member, *addr).map(Into::into), + Value::Func(f) => f.member_access(member), + _ => { + let (type_, methods) = match self { + Value::TypeObject(Type::Type(type_)) => { + (type_.as_ref().clone(), TYPE_METHODS.get(&type_.into())) + } + Value::TypeObject(type_) => (type_.clone(), STATIC_METHODS.get(&type_.into())), + _ => ( + self.get_type(), + INSTANCE_METHODS.get(&(&self.get_type()).into()), + ), + }; + let func = methods.and_then(|m| m.get(member)).ok_or(anyhow!( + "{} does not have member {}", + type_, + member + ))?; + Ok(Function::method(func.clone(), self).into()) + } + } + } } impl Add for Value { diff --git a/tests/interpreter_test.rs b/tests/interpreter_test.rs index 1fbb2d4..e17231d 100644 --- a/tests/interpreter_test.rs +++ b/tests/interpreter_test.rs @@ -41,6 +41,35 @@ async fn test_builtin_format() { _check_result(&mut env, "\"foo\".format()", Value::from("foo")).await; } +#[tokio::test] +async fn test_abi_encode_decode() { + let mut env = _create_env(); + + _check_result( + &mut env, + "abi.decode(abi.encode(1), (uint256))", + Value::from((1u64,)), + ) + .await; + + _check_result( + &mut env, + "abi.decode(abi.encode(1, \"foo\"), (int256, string))", + Value::from((1, "foo")), + ) + .await; +} + +#[tokio::test] +async fn test_type_max_min() { + let mut env = _create_env(); + _check_result(&mut env, "type(uint8).max", Value::from(u8::MAX as u64)).await; + _check_result(&mut env, "type(uint64).max", Value::from(u64::MAX)).await; + _check_result(&mut env, "type(uint256).min", Value::from(0u64)).await; + _check_result(&mut env, "type(int16).max", Value::from(i16::MAX as i32)).await; + _check_result(&mut env, "type(int8).min", Value::from(-128)).await; +} + #[tokio::test] async fn test_defined_functions() { let mut env = _create_env();