Add a ResolverApi interface.

This commit adds a `ResolverApi` trait to the `format` crate that can be
implemented by any type that can act as a resolver. A resolver is able
to provide information on the chain state. This chain state could be
fresh or it could be cached (which is something that we will do in a
future PR).

This cleans up our crate graph so that `format` is not depending on the
node interactions crate for the `EthereumNode` trait.
This commit is contained in:
Omar Abdulla
2025-07-24 15:01:00 +03:00
parent df6ad1e9e2
commit 2aaf2f54c7
10 changed files with 68 additions and 87 deletions
-1
View File
@@ -10,7 +10,6 @@ rust-version.workspace = true
[dependencies]
revive-dt-common = { workspace = true }
revive-dt-node-interaction = { workspace = true }
alloy = { workspace = true }
alloy-primitives = { workspace = true }
+26 -58
View File
@@ -12,9 +12,9 @@ use semver::VersionReq;
use serde::{Deserialize, Serialize};
use revive_dt_common::macros::define_wrapper_type;
use revive_dt_node_interaction::EthereumNode;
use crate::metadata::ContractInstance;
use crate::traits::ResolverApi;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct Input {
@@ -155,7 +155,7 @@ impl Calldata {
pub fn calldata(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
@@ -166,7 +166,7 @@ impl Calldata {
&self,
buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<()> {
match self {
Calldata::Single(bytes) => {
@@ -201,7 +201,7 @@ impl Calldata {
&self,
other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<bool> {
match self {
Calldata::Single(calldata) => Ok(calldata == other),
@@ -250,7 +250,7 @@ impl Input {
pub fn encoded_input(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<Bytes> {
match self.method {
Method::Deployer | Method::Fallback => {
@@ -317,7 +317,7 @@ impl Input {
pub fn legacy_transaction(
&self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<TransactionRequest> {
let input_data = self.encoded_input(deployed_contracts, chain_state_provider)?;
let transaction_request = TransactionRequest::default().from(self.caller).value(
@@ -364,7 +364,7 @@ pub const fn default_caller() -> Address {
fn resolve_argument(
value: &str,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl EthereumNode,
chain_state_provider: &impl ResolverApi,
) -> anyhow::Result<U256> {
if let Some(instance) = value.strip_suffix(".address") {
Ok(U256::from_be_slice(
@@ -433,31 +433,9 @@ mod tests {
use alloy_sol_types::SolValue;
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,
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
) -> 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!()
}
struct MockResolver;
impl ResolverApi for MockResolver {
fn chain_id(&self) -> anyhow::Result<alloy_primitives::ChainId> {
Ok(0x123)
}
@@ -529,7 +507,7 @@ mod tests {
(Address::ZERO, parsed_abi),
);
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
assert!(encoded.0.starts_with(&selector));
type T = (u64,);
@@ -573,7 +551,7 @@ mod tests {
(Address::ZERO, parsed_abi),
);
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,);
@@ -620,7 +598,7 @@ mod tests {
(Address::ZERO, parsed_abi),
);
let encoded = input.encoded_input(&contracts, &DummyEthereumNode).unwrap();
let encoded = input.encoded_input(&contracts, &MockResolver).unwrap();
assert!(encoded.0.starts_with(&selector));
type T = (alloy_primitives::Address,);
@@ -637,11 +615,11 @@ mod tests {
let input = "$CHAIN_ID";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(resolved, U256::from(DummyEthereumNode.chain_id().unwrap()))
assert_eq!(resolved, U256::from(MockResolver.chain_id().unwrap()))
}
#[test]
@@ -650,17 +628,13 @@ mod tests {
let input = "$GAS_LIMIT";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
DummyEthereumNode
.block_gas_limit(Default::default())
.unwrap()
)
U256::from(MockResolver.block_gas_limit(Default::default()).unwrap())
)
}
@@ -670,14 +644,14 @@ mod tests {
let input = "$COINBASE";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from_be_slice(
DummyEthereumNode
MockResolver
.block_coinbase(Default::default())
.unwrap()
.as_ref()
@@ -691,15 +665,13 @@ mod tests {
let input = "$DIFFICULTY";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
DummyEthereumNode
.block_difficulty(Default::default())
.unwrap()
MockResolver.block_difficulty(Default::default()).unwrap()
)
}
@@ -709,13 +681,13 @@ mod tests {
let input = "$BLOCK_HASH";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from_be_bytes(DummyEthereumNode.block_hash(Default::default()).unwrap().0)
U256::from_be_bytes(MockResolver.block_hash(Default::default()).unwrap().0)
)
}
@@ -725,13 +697,13 @@ mod tests {
let input = "$BLOCK_NUMBER";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(DummyEthereumNode.last_block_number().unwrap())
U256::from(MockResolver.last_block_number().unwrap())
)
}
@@ -741,17 +713,13 @@ mod tests {
let input = "$BLOCK_TIMESTAMP";
// Act
let resolved = resolve_argument(input, &Default::default(), &DummyEthereumNode);
let resolved = resolve_argument(input, &Default::default(), &MockResolver);
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
DummyEthereumNode
.block_timestamp(Default::default())
.unwrap()
)
U256::from(MockResolver.block_timestamp(Default::default()).unwrap())
)
}
}
+1
View File
@@ -5,3 +5,4 @@ pub mod corpus;
pub mod input;
pub mod metadata;
pub mod mode;
pub mod traits;
+30
View File
@@ -0,0 +1,30 @@
use alloy::eips::BlockNumberOrTag;
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
use anyhow::Result;
/// A trait of the interface are required to implement to be used by the resolution logic that this
/// crate implements to go from string calldata and into the bytes calldata.
pub trait ResolverApi {
/// Returns the ID of the chain that the node is on.
fn chain_id(&self) -> Result<ChainId>;
// TODO: This is currently a u128 due to Kitchensink needing more than 64 bits for its gas limit
// when we implement the changes to the gas we need to adjust this to be a u64.
/// Returns the gas limit of the specified block.
fn block_gas_limit(&self, number: BlockNumberOrTag) -> Result<u128>;
/// Returns the coinbase of the specified block.
fn block_coinbase(&self, number: BlockNumberOrTag) -> Result<Address>;
/// Returns the difficulty of the specified block.
fn block_difficulty(&self, number: BlockNumberOrTag) -> Result<U256>;
/// Returns the hash of the specified block.
fn block_hash(&self, number: BlockNumberOrTag) -> Result<BlockHash>;
/// Returns the timestamp of the specified block,
fn block_timestamp(&self, number: BlockNumberOrTag) -> Result<BlockTimestamp>;
/// Returns the number of the last block.
fn last_block_number(&self) -> Result<BlockNumber>;
}