Add resolution logic for other matterlabs variables

This commit is contained in:
Omar Abdulla
2025-07-14 23:51:59 +03:00
parent 6d7cd67931
commit 20da99784e
4 changed files with 258 additions and 43 deletions
Generated
+1
View File
@@ -3984,6 +3984,7 @@ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
"anyhow", "anyhow",
"revive-dt-node-interaction",
"semver 1.0.26", "semver 1.0.26",
"serde", "serde",
"serde_json", "serde_json",
+22 -16
View File
@@ -1,7 +1,7 @@
//! The test driver handles the compilation and execution of the test cases. //! The test driver handles the compilation and execution of the test cases.
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::TransactionBuilder; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::GethTrace; use alloy::rpc::types::trace::geth::GethTrace;
use alloy::{ use alloy::{
@@ -135,17 +135,21 @@ where
std::any::type_name::<T>() std::any::type_name::<T>()
); );
let tx = let tx = match input.legacy_transaction(
match input.legacy_transaction(nonce, &self.deployed_contracts, &self.deployed_abis) { nonce,
Ok(tx) => { &self.deployed_contracts,
tracing::debug!("Legacy transaction data: {tx:#?}"); &self.deployed_abis,
tx node,
} ) {
Err(err) => { Ok(tx) => {
tracing::error!("Failed to construct legacy transaction: {err:?}"); tracing::debug!("Legacy transaction data: {tx:#?}");
return Err(err); tx
} }
}; Err(err) => {
tracing::error!("Failed to construct legacy transaction: {err:?}");
return Err(err);
}
};
tracing::trace!("Executing transaction for input: {input:?}"); tracing::trace!("Executing transaction for input: {input:?}");
@@ -231,10 +235,12 @@ where
// automatically fill in all of the missing fields from the provider that we // automatically fill in all of the missing fields from the provider that we
// are using. // are using.
let code = alloy::hex::decode(&code)?; let code = alloy::hex::decode(&code)?;
let tx = TransactionRequest::default() let tx = {
.nonce(nonce) let tx = TransactionRequest::default()
.from(input.caller) .nonce(nonce)
.with_deploy_code(code); .from(input.caller);
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
};
let receipt = match node.execute_transaction(tx) { let receipt = match node.execute_transaction(tx) {
Ok(receipt) => receipt, Ok(receipt) => receipt,
+2
View File
@@ -9,6 +9,8 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
revive-dt-node-interaction = { workspace = true }
alloy = { workspace = true } alloy = { workspace = true }
alloy-primitives = { workspace = true } alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true } alloy-sol-types = { workspace = true }
+233 -27
View File
@@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use alloy::{ use alloy::{
eips::BlockNumberOrTag,
json_abi::JsonAbi, json_abi::JsonAbi,
network::TransactionBuilder, network::TransactionBuilder,
primitives::{Address, Bytes, U256}, primitives::{Address, Bytes, U256},
@@ -10,6 +11,8 @@ use semver::VersionReq;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use revive_dt_node_interaction::EthereumNode;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input { pub struct Input {
#[serde(default = "default_caller")] #[serde(default = "default_caller")]
@@ -84,6 +87,7 @@ impl Input {
&self, &self,
deployed_abis: &HashMap<String, JsonAbi>, deployed_abis: &HashMap<String, JsonAbi>,
deployed_contracts: &HashMap<String, Address>, deployed_contracts: &HashMap<String, Address>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
let Method::FunctionName(ref function_name) = self.method else { let Method::FunctionName(ref function_name) = self.method else {
return Ok(Bytes::default()); // fallback or deployer — no input return Ok(Bytes::default()); // fallback or deployer — no input
@@ -140,7 +144,7 @@ impl Input {
calldata.extend(function.selector().0); calldata.extend(function.selector().0);
for (arg_idx, arg) in calldata_args.iter().enumerate() { for (arg_idx, arg) in calldata_args.iter().enumerate() {
match resolve_argument(arg, deployed_contracts) { match resolve_argument(arg, deployed_contracts, chain_state_provider) {
Ok(resolved) => { Ok(resolved) => {
calldata.extend(resolved.to_be_bytes::<32>()); calldata.extend(resolved.to_be_bytes::<32>());
} }
@@ -160,8 +164,10 @@ impl Input {
nonce: u64, nonce: u64,
deployed_contracts: &HashMap<String, Address>, deployed_contracts: &HashMap<String, Address>,
deployed_abis: &HashMap<String, JsonAbi>, deployed_abis: &HashMap<String, JsonAbi>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_abis, deployed_contracts)?; let input_data =
self.encoded_input(deployed_abis, deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default().nonce(nonce); let transaction_request = TransactionRequest::default().nonce(nonce);
match self.method { match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
@@ -191,6 +197,7 @@ fn default_caller() -> Address {
fn resolve_argument( fn resolve_argument(
value: &str, value: &str,
deployed_contracts: &HashMap<String, Address>, deployed_contracts: &HashMap<String, Address>,
chain_state_provider: &impl EthereumNode,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
if let Some(instance) = value.strip_suffix(".address") { if let Some(instance) = value.strip_suffix(".address") {
Ok(U256::from_be_slice( Ok(U256::from_be_slice(
@@ -212,30 +219,40 @@ fn resolve_argument(
} else if let Some(value) = value.strip_prefix("0x") { } else if let Some(value) = value.strip_prefix("0x") {
Ok(U256::from_str_radix(value, 16) Ok(U256::from_str_radix(value, 16)
.map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))?) .map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))?)
} else { } else if value == "$CHAIN_ID" {
// TODO: This is a set of "variables" that we need to be able to resolve to be fully in let chain_id = chain_state_provider.chain_id()?;
// compliance with the matter labs tester but we currently do not resolve them. We need to Ok(U256::from(chain_id))
// add logic that does their resolution in the future, perhaps through some kind of system } else if value == "$GAS_LIMIT" {
// context API that we pass down to the resolution function that allows it to make calls to let gas_limit = chain_state_provider.block_gas_limit(BlockNumberOrTag::Latest)?;
// the node to perform these resolutions. Ok(U256::from(gas_limit))
let is_unsupported = [ } else if value == "$COINBASE" {
"$CHAIN_ID", let coinbase = chain_state_provider.block_coinbase(BlockNumberOrTag::Latest)?;
"$GAS_LIMIT", Ok(U256::from_be_slice(coinbase.as_ref()))
"$COINBASE", } else if value == "$DIFFICULTY" {
"$DIFFICULTY", let block_difficulty = chain_state_provider.block_difficulty(BlockNumberOrTag::Latest)?;
"$BLOCK_HASH", Ok(block_difficulty)
"$BLOCK_TIMESTAMP", } else if value.starts_with("$BLOCK_HASH") {
] let offset: u64 = value
.iter() .split(':')
.any(|var| value.starts_with(var)); .next_back()
.and_then(|value| value.parse().ok())
.unwrap_or_default();
if is_unsupported { let current_block_number = chain_state_provider.last_block_number()?;
tracing::error!(value, "Unsupported variable used"); let desired_block_number = current_block_number - offset;
anyhow::bail!("Encountered {value} which is currently unsupported by the framework");
} else { let block_hash = chain_state_provider.block_hash(desired_block_number.into())?;
Ok(U256::from_str_radix(value, 10)
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) 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))?)
} }
} }
@@ -248,6 +265,69 @@ mod tests {
use alloy_sol_types::SolValue; use alloy_sol_types::SolValue;
use std::collections::HashMap; use std::collections::HashMap;
struct DummyEthereumNode;
impl EthereumNode for DummyEthereumNode {
fn execute_transaction(
&self,
_: TransactionRequest,
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
unimplemented!()
}
fn trace_transaction(
&self,
_: alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
unimplemented!()
}
fn state_diff(
&self,
_: alloy::rpc::types::TransactionReceipt,
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
unimplemented!()
}
fn fetch_add_nonce(&self, _: Address) -> anyhow::Result<u64> {
unimplemented!()
}
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
Ok(0x123)
}
fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<u128> {
Ok(0x1234)
}
fn block_coinbase(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<Address> {
Ok(Address::ZERO)
}
fn block_difficulty(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result<U256> {
Ok(U256::from(0x12345u128))
}
fn block_hash(
&self,
_: alloy::eips::BlockNumberOrTag,
) -> anyhow::Result<alloy_primitives::BlockHash> {
Ok([0xEE; 32].into())
}
fn block_timestamp(
&self,
_: alloy::eips::BlockNumberOrTag,
) -> anyhow::Result<alloy_primitives::BlockTimestamp> {
Ok(0x123456)
}
fn last_block_number(&self) -> anyhow::Result<alloy_primitives::BlockNumber> {
Ok(0x1234567)
}
}
#[test] #[test]
fn test_encoded_input_uint256() { fn test_encoded_input_uint256() {
let raw_metadata = r#" let raw_metadata = r#"
@@ -283,7 +363,7 @@ mod tests {
let deployed_contracts = HashMap::new(); let deployed_contracts = HashMap::new();
let encoded = input let encoded = input
.encoded_input(&deployed_abis, &deployed_contracts) .encoded_input(&deployed_abis, &deployed_contracts, &DummyEthereumNode)
.unwrap(); .unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
@@ -326,7 +406,9 @@ mod tests {
abis.insert("Contract".to_string(), parsed_abi); abis.insert("Contract".to_string(), parsed_abi);
let contracts = HashMap::new(); let contracts = HashMap::new();
let encoded = input.encoded_input(&abis, &contracts).unwrap(); let encoded = input
.encoded_input(&abis, &contracts, &DummyEthereumNode)
.unwrap();
assert!(encoded.0.starts_with(&selector)); assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,); type T = (alloy_primitives::Address,);
@@ -336,4 +418,128 @@ mod tests {
address!("0x1000000000000000000000000000000000000001") address!("0x1000000000000000000000000000000000000001")
); );
} }
#[test]
fn resolver_can_resolve_chain_id_variable() {
// Arrange
let input = "$CHAIN_ID";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(resolved, U256::from(DummyEthereumNode.chain_id().unwrap()))
}
#[test]
fn resolver_can_resolve_gas_limit_variable() {
// Arrange
let input = "$GAS_LIMIT";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
DummyEthereumNode
.block_gas_limit(Default::default())
.unwrap()
)
)
}
#[test]
fn resolver_can_resolve_coinbase_variable() {
// Arrange
let input = "$COINBASE";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from_be_slice(
DummyEthereumNode
.block_coinbase(Default::default())
.unwrap()
.as_ref()
)
)
}
#[test]
fn resolver_can_resolve_block_difficulty_variable() {
// Arrange
let input = "$DIFFICULTY";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
DummyEthereumNode
.block_difficulty(Default::default())
.unwrap()
)
}
#[test]
fn resolver_can_resolve_block_hash_variable() {
// Arrange
let input = "$BLOCK_HASH";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from_be_bytes(DummyEthereumNode.block_hash(Default::default()).unwrap().0)
)
}
#[test]
fn resolver_can_resolve_block_number_variable() {
// Arrange
let input = "$BLOCK_NUMBER";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(DummyEthereumNode.last_block_number().unwrap())
)
}
#[test]
fn resolver_can_resolve_block_timestamp_variable() {
// Arrange
let input = "$BLOCK_TIMESTAMP";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
DummyEthereumNode
.block_timestamp(Default::default())
.unwrap()
)
)
}
} }