diff --git a/crates/common/src/macros/define_wrapper_type.rs b/crates/common/src/macros/define_wrapper_type.rs index 28a9e53..7eb28bc 100644 --- a/crates/common/src/macros/define_wrapper_type.rs +++ b/crates/common/src/macros/define_wrapper_type.rs @@ -48,11 +48,7 @@ macro_rules! define_wrapper_type { $vis struct $ident($ty); impl $ident { - pub fn new(value: $ty) -> Self { - Self(value) - } - - pub fn new_from>(value: T) -> Self { + pub fn new(value: impl Into<$ty>) -> Self { Self(value.into()) } diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index a78100b..5267c52 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -509,7 +509,7 @@ where .iter() .zip(actual_event.topics()) { - let expected = Calldata::Compound(vec![expected.clone()]); + let expected = Calldata::new_compound([expected]); if !expected.is_equivalent( &actual.0, deployed_contracts, @@ -718,7 +718,7 @@ where ); let _guard = tracing_span.enter(); - let case_idx = CaseIdx::new_from(case_idx); + let case_idx = CaseIdx::new(case_idx); // For inputs if one of the inputs fail we move on to the next case (we do not move // on to the next input as it doesn't make sense. It depends on the previous one). diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 7693e7e..78f4693 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -8,6 +8,7 @@ use alloy::{ rpc::types::TransactionRequest, }; use alloy_primitives::{FixedBytes, utils::parse_units}; +use anyhow::Context; use semver::VersionReq; use serde::{Deserialize, Serialize}; @@ -55,11 +56,86 @@ pub struct Event { pub values: Calldata, } +/// A type definition for the calldata supported by the testing framework. +/// +/// We choose to document all of the types used in [`Calldata`] in this one doc comment to elaborate +/// on why they exist and consolidate all of the documentation for calldata in a single place where +/// it can be viewed and understood. +/// +/// The [`Single`] variant of this enum is quite simple and straightforward: it's a hex-encoded byte +/// array of the calldata. +/// +/// The [`Compound`] type is more intricate and allows for capabilities such as resolution and some +/// simple arithmetic operations. It houses a vector of [`CalldataItem`]s which is just a wrapper +/// around an owned string. +/// +/// A [`CalldataItem`] could be a simple hex string of a single calldata argument, but it could also +/// be something that requires resolution such as `MyContract.address` which is a variable that is +/// understood by the resolution logic to mean "Lookup the address of this particular contract +/// instance". +/// +/// In addition to the above, the format supports some simple arithmetic operations like add, sub, +/// divide, multiply, bitwise AND, bitwise OR, and bitwise XOR. Our parser understands the [reverse +/// polish notation] simply because it's easy to write a calculator for that notation and since we +/// do not have plans to use arithmetic too often in tests. In reverse polish notation a typical +/// `2 + 4` would be written as `2 4 +` which makes this notation very simple to implement through +/// a stack. +/// +/// Combining the above, a single [`CalldataItem`] could employ both resolution and arithmetic at +/// the same time. For example, a [`CalldataItem`] of `$BLOCK_NUMBER $BLOCK_NUMBER +` means that +/// the block number should be retrieved and then it should be added to itself. +/// +/// Internally, we split the [`CalldataItem`] by spaces. Therefore, `$BLOCK_NUMBER $BLOCK_NUMBER+` +/// is invalid but `$BLOCK_NUMBER $BLOCK_NUMBER +` is valid and can be understood by the parser and +/// calculator. After the split is done, each token is parsed into a [`CalldataToken<&str>`] forming +/// an [`Iterator`] over [`CalldataToken<&str>`]. A [`CalldataToken<&str>`] can then be resolved +/// into a [`CalldataToken`] through the resolution logic. Finally, after resolution is done, +/// this iterator of [`CalldataToken`] is collapsed into the final result by applying the +/// arithmetic operations requested. +/// +/// For example, supplying a [`Compound`] calldata of `0xdeadbeef` produces an iterator of a single +/// [`CalldataToken<&str>`] items of the value [`CalldataToken::Item`] of the string value 12 which +/// we can then resolve into the appropriate [`U256`] value and convert into calldata. +/// +/// In summary, the various types used in [`Calldata`] represent the following: +/// - [`CalldataItem`]: A calldata string from the metadata files. +/// - [`CalldataToken<&str>`]: Typically used in an iterator of items from the space splitted +/// [`CalldataItem`] and represents a token that has not yet been resolved into its value. +/// - [`CalldataToken`]: Represents a token that's been resolved from being a string and into +/// the word-size calldata argument on which we can perform arithmetic. +/// +/// [`Single`]: Calldata::Single +/// [`Compound`]: Calldata::Compound +/// [reverse polish notation]: https://en.wikipedia.org/wiki/Reverse_Polish_notation #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[serde(untagged)] pub enum Calldata { Single(Bytes), - Compound(Vec), + Compound(Vec), +} + +define_wrapper_type! { + /// This represents an item in the [`Calldata::Compound`] variant. + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] + #[serde(transparent)] + pub struct CalldataItem(String); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +enum CalldataToken { + Item(T), + Operation(Operation), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +enum Operation { + Addition, + Subtraction, + Multiplication, + Division, + BitwiseAnd, + BitwiseOr, + BitwiseXor, } /// Specify how the contract is called. @@ -91,12 +167,12 @@ define_wrapper_type!( impl Input { pub const fn default_caller() -> Address { Address(FixedBytes(alloy::hex!( - "90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" + "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" ))) } fn default_instance() -> ContractInstance { - ContractInstance::new_from("Test") + ContractInstance::new("Test") } fn instance_to_address( @@ -234,11 +310,27 @@ impl Default for Calldata { } impl Calldata { + pub fn new_single(item: impl Into) -> Self { + Self::Single(item.into()) + } + + pub fn new_compound(items: impl IntoIterator>) -> Self { + Self::Compound( + items + .into_iter() + .map(|item| item.as_ref().to_owned()) + .map(CalldataItem::new) + .collect(), + ) + } + pub fn find_all_contract_instances(&self, vec: &mut Vec) { if let Calldata::Compound(compound) = self { for item in compound { - if let Some(instance) = item.strip_suffix(".address") { - vec.push(ContractInstance::new_from(instance)) + if let Some(instance) = + item.strip_suffix(CalldataToken::<()>::ADDRESS_VARIABLE_SUFFIX) + { + vec.push(ContractInstance::new(instance)) } } } @@ -266,12 +358,12 @@ impl Calldata { } Calldata::Compound(items) => { for (arg_idx, arg) in items.iter().enumerate() { - match resolve_argument(arg, deployed_contracts, chain_state_provider) { + match arg.resolve(deployed_contracts, chain_state_provider) { Ok(resolved) => { buffer.extend(resolved.to_be_bytes::<32>()); } Err(error) => { - tracing::error!(arg, arg_idx, ?error, "Failed to resolve argument"); + tracing::error!(?arg, arg_idx, ?error, "Failed to resolve argument"); return Err(error); } }; @@ -303,7 +395,7 @@ impl Calldata { for (this, other) in items.iter().zip(other.chunks(32)) { // The matterlabs format supports wildcards and therefore we // also need to support them. - if this == "*" { + if this.as_ref() == "*" { continue; } @@ -315,7 +407,7 @@ impl Calldata { std::borrow::Cow::Borrowed(other) }; - let this = resolve_argument(this, deployed_contracts, chain_state_provider)?; + let this = this.resolve(deployed_contracts, chain_state_provider)?; let other = U256::from_be_slice(&other); if this != other { return Ok(false); @@ -327,6 +419,177 @@ impl Calldata { } } +impl CalldataItem { + fn resolve( + &self, + deployed_contracts: &HashMap, + chain_state_provider: &impl ResolverApi, + ) -> anyhow::Result { + let mut stack = Vec::>::new(); + + for token in self + .calldata_tokens() + .map(|token| token.resolve(deployed_contracts, chain_state_provider)) + { + let token = token?; + let new_token = match token { + CalldataToken::Item(_) => token, + CalldataToken::Operation(operation) => { + let right_operand = stack + .pop() + .and_then(CalldataToken::into_item) + .context("Invalid calldata arithmetic operation")?; + let left_operand = stack + .pop() + .and_then(CalldataToken::into_item) + .context("Invalid calldata arithmetic operation")?; + + let result = match operation { + Operation::Addition => left_operand.checked_add(right_operand), + Operation::Subtraction => left_operand.checked_sub(right_operand), + Operation::Multiplication => left_operand.checked_mul(right_operand), + Operation::Division => left_operand.checked_div(right_operand), + Operation::BitwiseAnd => Some(left_operand & right_operand), + Operation::BitwiseOr => Some(left_operand | right_operand), + Operation::BitwiseXor => Some(left_operand ^ right_operand), + } + .context("Invalid calldata arithmetic operation")?; + + CalldataToken::Item(result) + } + }; + stack.push(new_token) + } + + match stack.as_slice() { + // Empty stack means that we got an empty compound calldata which we resolve to zero. + [] => Ok(U256::ZERO), + [CalldataToken::Item(item)] => Ok(*item), + _ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation")), + } + } + + fn calldata_tokens<'a>(&'a self) -> impl Iterator> + 'a { + self.0.split(' ').map(|item| match item { + "+" => CalldataToken::Operation(Operation::Addition), + "-" => CalldataToken::Operation(Operation::Subtraction), + "/" => CalldataToken::Operation(Operation::Division), + "*" => CalldataToken::Operation(Operation::Multiplication), + "&" => CalldataToken::Operation(Operation::BitwiseAnd), + "|" => CalldataToken::Operation(Operation::BitwiseOr), + "^" => CalldataToken::Operation(Operation::BitwiseXor), + _ => CalldataToken::Item(item), + }) + } +} + +impl CalldataToken { + const ADDRESS_VARIABLE_SUFFIX: &str = ".address"; + const NEGATIVE_VALUE_PREFIX: char = '-'; + const HEX_LITERAL_PREFIX: &str = "0x"; + const CHAIN_VARIABLE: &str = "$CHAIN_ID"; + const GAS_LIMIT_VARIABLE: &str = "$GAS_LIMIT"; + const COINBASE_VARIABLE: &str = "$COINBASE"; + const DIFFICULTY_VARIABLE: &str = "$DIFFICULTY"; + const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH"; + const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER"; + const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP"; + + fn into_item(self) -> Option { + match self { + CalldataToken::Item(item) => Some(item), + CalldataToken::Operation(_) => None, + } + } +} + +impl> CalldataToken { + /// This function takes in the string calldata argument provided in the JSON input and resolves + /// it into a [`U256`] which is later used to construct the calldata. + /// + /// # Note + /// + /// This piece of code is taken from the matter-labs-tester repository which is licensed under + /// MIT or Apache. The original source code can be found here: + /// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 + fn resolve( + self, + deployed_contracts: &HashMap, + chain_state_provider: &impl ResolverApi, + ) -> anyhow::Result> { + match self { + Self::Item(item) => { + let item = item.as_ref(); + let value = if let Some(instance) = item.strip_suffix(Self::ADDRESS_VARIABLE_SUFFIX) + { + Ok(U256::from_be_slice( + deployed_contracts + .get(&ContractInstance::new(instance)) + .map(|(a, _)| *a) + .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? + .as_ref(), + )) + } else if let Some(value) = item.strip_prefix(Self::NEGATIVE_VALUE_PREFIX) { + let value = U256::from_str_radix(value, 10).map_err(|error| { + anyhow::anyhow!("Invalid decimal literal after `-`: {}", error) + })?; + if value > U256::ONE << 255u8 { + anyhow::bail!("Decimal literal after `-` is too big"); + } + let value = value + .checked_sub(U256::ONE) + .ok_or_else(|| anyhow::anyhow!("`-0` is invalid literal"))?; + Ok(U256::MAX.checked_sub(value).expect("Always valid")) + } else if let Some(value) = item.strip_prefix(Self::HEX_LITERAL_PREFIX) { + Ok(U256::from_str_radix(value, 16).map_err(|error| { + anyhow::anyhow!("Invalid hexadecimal literal: {}", error) + })?) + } else if item == Self::CHAIN_VARIABLE { + let chain_id = chain_state_provider.chain_id()?; + Ok(U256::from(chain_id)) + } else if item == Self::GAS_LIMIT_VARIABLE { + let gas_limit = + chain_state_provider.block_gas_limit(BlockNumberOrTag::Latest)?; + Ok(U256::from(gas_limit)) + } else if item == Self::COINBASE_VARIABLE { + let coinbase = chain_state_provider.block_coinbase(BlockNumberOrTag::Latest)?; + Ok(U256::from_be_slice(coinbase.as_ref())) + } else if item == Self::DIFFICULTY_VARIABLE { + let block_difficulty = + chain_state_provider.block_difficulty(BlockNumberOrTag::Latest)?; + Ok(block_difficulty) + } else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) { + let offset: u64 = item + .split(':') + .next_back() + .and_then(|value| value.parse().ok()) + .unwrap_or_default(); + + let current_block_number = chain_state_provider.last_block_number()?; + let desired_block_number = current_block_number - offset; + + let block_hash = + chain_state_provider.block_hash(desired_block_number.into())?; + + Ok(U256::from_be_bytes(block_hash.0)) + } else if item == Self::BLOCK_NUMBER_VARIABLE { + let current_block_number = chain_state_provider.last_block_number()?; + Ok(U256::from(current_block_number)) + } else if item == Self::BLOCK_TIMESTAMP_VARIABLE { + let timestamp = + chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?; + Ok(U256::from(timestamp)) + } else { + Ok(U256::from_str_radix(item, 10) + .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) + }; + value.map(CalldataToken::Item) + } + Self::Operation(operation) => Ok(CalldataToken::Operation(operation)), + } + } +} + impl Serialize for EtherValue { fn serialize(&self, serializer: S) -> Result where @@ -353,77 +616,6 @@ impl<'de> Deserialize<'de> for EtherValue { } } -/// This function takes in the string calldata argument provided in the JSON input and resolves it -/// into a [`U256`] which is later used to construct the calldata. -/// -/// # Note -/// -/// This piece of code is taken from the matter-labs-tester repository which is licensed under MIT -/// or Apache. The original source code can be found here: -/// https://github.com/matter-labs/era-compiler-tester/blob/0ed598a27f6eceee7008deab3ff2311075a2ec69/compiler_tester/src/test/case/input/value.rs#L43-L146 -fn resolve_argument( - value: &str, - deployed_contracts: &HashMap, - chain_state_provider: &impl ResolverApi, -) -> anyhow::Result { - if let Some(instance) = value.strip_suffix(".address") { - Ok(U256::from_be_slice( - deployed_contracts - .get(&ContractInstance::new_from(instance)) - .map(|(a, _)| *a) - .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? - .as_ref(), - )) - } else if let Some(value) = value.strip_prefix('-') { - let value = U256::from_str_radix(value, 10) - .map_err(|error| anyhow::anyhow!("Invalid decimal literal after `-`: {}", error))?; - if value > U256::ONE << 255u8 { - anyhow::bail!("Decimal literal after `-` is too big"); - } - let value = value - .checked_sub(U256::ONE) - .ok_or_else(|| anyhow::anyhow!("`-0` is invalid literal"))?; - Ok(U256::MAX.checked_sub(value).expect("Always valid")) - } else if let Some(value) = value.strip_prefix("0x") { - Ok(U256::from_str_radix(value, 16) - .map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))?) - } else if value == "$CHAIN_ID" { - let chain_id = chain_state_provider.chain_id()?; - Ok(U256::from(chain_id)) - } else if value == "$GAS_LIMIT" { - let gas_limit = chain_state_provider.block_gas_limit(BlockNumberOrTag::Latest)?; - Ok(U256::from(gas_limit)) - } else if value == "$COINBASE" { - let coinbase = chain_state_provider.block_coinbase(BlockNumberOrTag::Latest)?; - Ok(U256::from_be_slice(coinbase.as_ref())) - } else if value == "$DIFFICULTY" { - let block_difficulty = chain_state_provider.block_difficulty(BlockNumberOrTag::Latest)?; - Ok(block_difficulty) - } else if value.starts_with("$BLOCK_HASH") { - let offset: u64 = value - .split(':') - .next_back() - .and_then(|value| value.parse().ok()) - .unwrap_or_default(); - - let current_block_number = chain_state_provider.last_block_number()?; - let desired_block_number = current_block_number - offset; - - let block_hash = chain_state_provider.block_hash(desired_block_number.into())?; - - Ok(U256::from_be_bytes(block_hash.0)) - } else if value == "$BLOCK_NUMBER" { - let current_block_number = chain_state_provider.last_block_number()?; - Ok(U256::from(current_block_number)) - } else if value == "$BLOCK_TIMESTAMP" { - let timestamp = chain_state_provider.block_timestamp(BlockNumberOrTag::Latest)?; - Ok(U256::from(timestamp)) - } else { - Ok(U256::from_str_radix(value, 10) - .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) - } -} - #[cfg(test)] mod tests { @@ -495,15 +687,15 @@ mod tests { .0; let input = Input { - instance: ContractInstance::new_from("Contract"), + instance: ContractInstance::new("Contract"), method: Method::FunctionName("store".to_owned()), - calldata: Calldata::Compound(vec!["42".into()]), + calldata: Calldata::new_compound(["42"]), ..Default::default() }; let mut contracts = HashMap::new(); contracts.insert( - ContractInstance::new_from("Contract"), + ContractInstance::new("Contract"), (Address::ZERO, parsed_abi), ); @@ -539,15 +731,13 @@ mod tests { let input: Input = Input { instance: "Contract".to_owned().into(), method: Method::FunctionName("send(address)".to_owned()), - calldata: Calldata::Compound(vec![ - "0x1000000000000000000000000000000000000001".to_string(), - ]), + calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]), ..Default::default() }; let mut contracts = HashMap::new(); contracts.insert( - ContractInstance::new_from("Contract"), + ContractInstance::new("Contract"), (Address::ZERO, parsed_abi), ); @@ -584,17 +774,15 @@ mod tests { .0; let input: Input = Input { - instance: ContractInstance::new_from("Contract"), + instance: ContractInstance::new("Contract"), method: Method::FunctionName("send".to_owned()), - calldata: Calldata::Compound(vec![ - "0x1000000000000000000000000000000000000001".to_string(), - ]), + calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]), ..Default::default() }; let mut contracts = HashMap::new(); contracts.insert( - ContractInstance::new_from("Contract"), + ContractInstance::new("Contract"), (Address::ZERO, parsed_abi), ); @@ -609,13 +797,21 @@ mod tests { ); } + fn resolve_calldata_item( + input: &str, + deployed_contracts: &HashMap, + chain_state_provider: &impl ResolverApi, + ) -> anyhow::Result { + CalldataItem::new(input).resolve(deployed_contracts, chain_state_provider) + } + #[test] fn resolver_can_resolve_chain_id_variable() { // Arrange let input = "$CHAIN_ID"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -628,7 +824,7 @@ mod tests { let input = "$GAS_LIMIT"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -644,7 +840,7 @@ mod tests { let input = "$COINBASE"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -665,7 +861,7 @@ mod tests { let input = "$DIFFICULTY"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -681,7 +877,7 @@ mod tests { let input = "$BLOCK_HASH"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -697,7 +893,7 @@ mod tests { let input = "$BLOCK_NUMBER"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -713,7 +909,7 @@ mod tests { let input = "$BLOCK_TIMESTAMP"; // Act - let resolved = resolve_argument(input, &Default::default(), &MockResolver); + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); // Assert let resolved = resolved.expect("Failed to resolve argument"); @@ -722,4 +918,96 @@ mod tests { U256::from(MockResolver.block_timestamp(Default::default()).unwrap()) ) } + + #[test] + fn simple_addition_can_be_resolved() { + // Arrange + let input = "2 4 +"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!(resolved, U256::from(6)); + } + + #[test] + fn simple_subtraction_can_be_resolved() { + // Arrange + let input = "4 2 -"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!(resolved, U256::from(2)); + } + + #[test] + fn simple_multiplication_can_be_resolved() { + // Arrange + let input = "4 2 *"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!(resolved, U256::from(8)); + } + + #[test] + fn simple_division_can_be_resolved() { + // Arrange + let input = "4 2 /"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!(resolved, U256::from(2)); + } + + #[test] + fn arithmetic_errors_are_not_panics() { + // Arrange + let input = "4 0 /"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + assert!(resolved.is_err()) + } + + #[test] + fn arithmetic_with_resolution_works() { + // Arrange + let input = "$BLOCK_NUMBER 10 +"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!( + resolved, + U256::from(MockResolver.last_block_number().unwrap() + 10) + ); + } + + #[test] + fn incorrect_number_of_arguments_errors() { + // Arrange + let input = "$BLOCK_NUMBER 10 + +"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver); + + // Assert + assert!(resolved.is_err()) + } } diff --git a/crates/format/src/metadata.rs b/crates/format/src/metadata.rs index f6de7d7..9329002 100644 --- a/crates/format/src/metadata.rs +++ b/crates/format/src/metadata.rs @@ -193,10 +193,10 @@ impl Metadata { metadata.file_path = Some(path.to_path_buf()); metadata.contracts = Some( [( - ContractInstance::new_from("test"), + ContractInstance::new("test"), ContractPathAndIdentifier { contract_source_path: path.to_path_buf(), - contract_ident: ContractIdent::new_from("Test"), + contract_ident: ContractIdent::new("Test"), }, )] .into(),