diff --git a/crates/core/src/driver/mod.rs b/crates/core/src/driver/mod.rs index 7779c58..c070d38 100644 --- a/crates/core/src/driver/mod.rs +++ b/crates/core/src/driver/mod.rs @@ -4,11 +4,10 @@ use std::collections::HashMap; use std::marker::PhantomData; use std::path::PathBuf; -use alloy::eips::BlockNumberOrTag; use alloy::hex; use alloy::json_abi::JsonAbi; use alloy::network::{Ethereum, TransactionBuilder}; -use alloy::primitives::{BlockNumber, U256}; +use alloy::primitives::U256; use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::trace::geth::{ CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace, @@ -23,7 +22,7 @@ use alloy::{ }; use anyhow::Context; use indexmap::IndexMap; -use revive_dt_format::traits::ResolverApi; +use revive_dt_format::traits::{ResolutionContext, ResolverApi}; use semver::Version; use revive_dt_format::case::{Case, CaseIdx}; @@ -88,13 +87,7 @@ where .handle_input_call_frame_tracing(&execution_receipt, node) .await?; self.handle_input_variable_assignment(input, &tracing_result)?; - let resolver = BlockPinnedResolver::<'_, T> { - node, - block_number: execution_receipt - .block_number - .context("Transaction was not included in a block")?, - }; - self.handle_input_expectations(input, &execution_receipt, &resolver, &tracing_result) + self.handle_input_expectations(input, &execution_receipt, node, &tracing_result) .await?; self.handle_input_diff(case_idx, execution_receipt, node) .await @@ -171,7 +164,7 @@ where .context("Failed to find deployment receipt"), Method::Fallback | Method::FunctionName(_) => { let tx = match input - .legacy_transaction(&self.deployed_contracts, &self.variables, node) + .legacy_transaction(node, self.default_resolution_context()) .await { Ok(tx) => { @@ -317,8 +310,10 @@ where } } - let deployed_contracts = &mut self.deployed_contracts; - let variables = &mut self.variables; + let resolution_context = self + .default_resolution_context() + .with_block_number(execution_receipt.block_number.as_ref()) + .with_transaction_hash(&execution_receipt.transaction_hash); // Handling the receipt state assertion. let expected = !expectation.exception; @@ -341,7 +336,7 @@ where let expected = expected_calldata; let actual = &tracing_result.output.as_ref().unwrap_or_default(); if !expected - .is_equivalent(actual, deployed_contracts, &*variables, resolver) + .is_equivalent(actual, resolver, resolution_context) .await? { tracing::error!( @@ -376,7 +371,7 @@ where if let Some(ref expected_address) = expected_event.address { let expected = Address::from_slice( Calldata::new_compound([expected_address]) - .calldata(deployed_contracts, &*variables, resolver) + .calldata(resolver, resolution_context) .await? .get(12..32) .expect("Can't fail"), @@ -404,7 +399,7 @@ where { let expected = Calldata::new_compound([expected]); if !expected - .is_equivalent(&actual.0, deployed_contracts, &*variables, resolver) + .is_equivalent(&actual.0, resolver, resolution_context) .await? { tracing::error!( @@ -424,7 +419,7 @@ where let expected = &expected_event.values; let actual = &actual_event.data().data; if !expected - .is_equivalent(&actual.0, deployed_contracts, &*variables, resolver) + .is_equivalent(&actual.0, resolver, resolution_context) .await? { tracing::error!( @@ -530,7 +525,7 @@ where if let Some(calldata) = calldata { let calldata = calldata - .calldata(&self.deployed_contracts, None, node) + .calldata(node, self.default_resolution_context()) .await?; code.extend(calldata); } @@ -571,6 +566,12 @@ where Ok((address, abi, Some(receipt))) } + + fn default_resolution_context(&self) -> ResolutionContext<'_> { + ResolutionContext::default() + .with_deployed_contracts(&self.deployed_contracts) + .with_variables(&self.variables) + } } pub struct CaseDriver<'a, Leader: Platform, Follower: Platform> { @@ -685,67 +686,3 @@ where Ok(inputs_executed) } } - -pub struct BlockPinnedResolver<'a, T: Platform> { - block_number: BlockNumber, - node: &'a T::Blockchain, -} - -impl<'a, T: Platform> ResolverApi for BlockPinnedResolver<'a, T> { - async fn chain_id(&self) -> anyhow::Result { - self.node.chain_id().await - } - - async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { - self.node - .block_gas_limit(self.resolve_block_number_or_tag(number)) - .await - } - - async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result
{ - self.node - .block_coinbase(self.resolve_block_number_or_tag(number)) - .await - } - - async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result { - self.node - .block_difficulty(self.resolve_block_number_or_tag(number)) - .await - } - - async fn block_hash( - &self, - number: BlockNumberOrTag, - ) -> anyhow::Result { - self.node - .block_hash(self.resolve_block_number_or_tag(number)) - .await - } - - async fn block_timestamp( - &self, - number: BlockNumberOrTag, - ) -> anyhow::Result { - self.node - .block_timestamp(self.resolve_block_number_or_tag(number)) - .await - } - - async fn last_block_number(&self) -> anyhow::Result { - Ok(self.block_number) - } -} - -impl<'a, T: Platform> BlockPinnedResolver<'a, T> { - fn resolve_block_number_or_tag(&self, number: BlockNumberOrTag) -> BlockNumberOrTag { - match number { - BlockNumberOrTag::Latest => BlockNumberOrTag::Number(self.block_number), - n @ BlockNumberOrTag::Finalized - | n @ BlockNumberOrTag::Safe - | n @ BlockNumberOrTag::Earliest - | n @ BlockNumberOrTag::Pending - | n @ BlockNumberOrTag::Number(_) => n, - } - } -} diff --git a/crates/format/src/input.rs b/crates/format/src/input.rs index 32b96b7..56ee955 100644 --- a/crates/format/src/input.rs +++ b/crates/format/src/input.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use alloy::{ eips::BlockNumberOrTag, hex::ToHexExt, - json_abi::JsonAbi, network::TransactionBuilder, primitives::{Address, Bytes, U256}, rpc::types::TransactionRequest, @@ -15,8 +14,8 @@ use serde::{Deserialize, Serialize}; use revive_dt_common::macros::define_wrapper_type; -use crate::metadata::ContractInstance; use crate::traits::ResolverApi; +use crate::{metadata::ContractInstance, traits::ResolutionContext}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] pub struct Input { @@ -187,37 +186,21 @@ impl Input { ContractInstance::new("Test") } - fn instance_to_address( + pub async fn encoded_input( &self, - instance: &ContractInstance, - deployed_contracts: &HashMap, - ) -> anyhow::Result
{ - deployed_contracts - .get(instance) - .map(|(a, _)| *a) - .ok_or_else(|| anyhow::anyhow!("instance {instance:?} not deployed")) - } - - pub async fn encoded_input<'a>( - &'a self, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result { match self.method { Method::Deployer | Method::Fallback => { - let calldata = self - .calldata - .calldata(deployed_contracts, variables, resolver) - .await?; + let calldata = self.calldata.calldata(resolver, context).await?; Ok(calldata.into()) } Method::FunctionName(ref function_name) => { - let Some(abi) = deployed_contracts.get(&self.instance).map(|(_, a)| a) else { + let Some(abi) = context.deployed_contract_abi(&self.instance) else { tracing::error!( contract_name = self.instance.as_ref(), - available_abis = ?deployed_contracts.keys().collect::>(), "Attempted to lookup ABI of contract but it wasn't found" ); anyhow::bail!("ABI for instance '{}' not found", self.instance.as_ref()); @@ -256,7 +239,7 @@ impl Input { let mut calldata = Vec::::with_capacity(4 + self.calldata.size_requirement()); calldata.extend(function.selector().0); self.calldata - .calldata_into_slice(&mut calldata, deployed_contracts, variables, resolver) + .calldata_into_slice(&mut calldata, resolver, context) .await?; Ok(calldata.into()) @@ -265,15 +248,12 @@ impl Input { } /// Parse this input into a legacy transaction. - pub async fn legacy_transaction<'a>( - &'a self, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, + pub async fn legacy_transaction( + &self, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result { - let input_data = self - .encoded_input(deployed_contracts, variables, resolver) - .await?; + let input_data = self.encoded_input(resolver, context).await?; let transaction_request = TransactionRequest::default().from(self.caller).value( self.value .map(|value| value.into_inner()) @@ -282,7 +262,10 @@ impl Input { match self.method { Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)), _ => Ok(transaction_request - .to(self.instance_to_address(&self.instance, deployed_contracts)?) + .to(context + .deployed_contract_address(&self.instance) + .context("Failed to get the contract address") + .copied()?) .input(input_data.into())), } } @@ -351,24 +334,22 @@ impl Calldata { } } - pub async fn calldata<'a>( - &'a self, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, + pub async fn calldata( + &self, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result> { let mut buffer = Vec::::with_capacity(self.size_requirement()); - self.calldata_into_slice(&mut buffer, deployed_contracts, variables, resolver) + self.calldata_into_slice(&mut buffer, resolver, context) .await?; Ok(buffer) } - pub async fn calldata_into_slice<'a>( - &'a self, + pub async fn calldata_into_slice( + &self, buffer: &mut Vec, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result<()> { match self { Calldata::Single(bytes) => { @@ -376,10 +357,7 @@ impl Calldata { } Calldata::Compound(items) => { for (arg_idx, arg) in items.iter().enumerate() { - match arg - .resolve(deployed_contracts, variables.clone(), resolver) - .await - { + match arg.resolve(resolver, context).await { Ok(resolved) => { buffer.extend(resolved.to_be_bytes::<32>()); } @@ -402,12 +380,11 @@ impl Calldata { } /// Checks if this [`Calldata`] is equivalent to the passed calldata bytes. - pub async fn is_equivalent<'a>( - &'a self, + pub async fn is_equivalent( + &self, other: &[u8], - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result { match self { Calldata::Single(calldata) => Ok(calldata == other), @@ -429,9 +406,7 @@ impl Calldata { std::borrow::Cow::Borrowed(other) }; - let this = this - .resolve(deployed_contracts, variables.clone(), resolver) - .await?; + let this = this.resolve(resolver, context).await?; let other = U256::from_be_slice(&other); if this != other { return Ok(false); @@ -444,17 +419,16 @@ impl Calldata { } impl CalldataItem { - async fn resolve<'a>( - &'a self, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, + async fn resolve( + &self, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> anyhow::Result { let mut stack = Vec::>::new(); for token in self .calldata_tokens() - .map(|token| token.resolve(deployed_contracts, variables.clone(), resolver)) + .map(|token| token.resolve(resolver, context)) { let token = token.await?; let new_token = match token { @@ -509,7 +483,7 @@ impl CalldataItem { } } - fn calldata_tokens<'a>(&'a self) -> impl Iterator> + 'a { + fn calldata_tokens(&self) -> impl Iterator> { self.0.split(' ').map(|item| match item { "+" => CalldataToken::Operation(Operation::Addition), "-" => CalldataToken::Operation(Operation::Subtraction), @@ -533,9 +507,11 @@ impl CalldataToken { const GAS_LIMIT_VARIABLE: &str = "$GAS_LIMIT"; const COINBASE_VARIABLE: &str = "$COINBASE"; const DIFFICULTY_VARIABLE: &str = "$DIFFICULTY"; + const BLOCK_BASE_FEE_VARIABLE: &str = "$BASE_FEE"; const BLOCK_HASH_VARIABLE_PREFIX: &str = "$BLOCK_HASH"; const BLOCK_NUMBER_VARIABLE: &str = "$BLOCK_NUMBER"; const BLOCK_TIMESTAMP_VARIABLE: &str = "$BLOCK_TIMESTAMP"; + const TRANSACTION_GAS_PRICE: &str = "$TRANSACTION_GAS_PRICE"; const VARIABLE_PREFIX: &str = "$VARIABLE:"; fn into_item(self) -> Option { @@ -555,24 +531,21 @@ impl> CalldataToken { /// 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 - async fn resolve<'a>( + async fn resolve( self, - deployed_contracts: &HashMap, - variables: impl Into>> + Clone, resolver: &impl ResolverApi, + context: ResolutionContext<'_>, ) -> 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(), - )) + context + .deployed_contract_address(&ContractInstance::new(instance)) + .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance)) + .map(AsRef::as_ref) + .map(U256::from_be_slice) } 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) @@ -585,22 +558,36 @@ impl> CalldataToken { .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) - })?) + U256::from_str_radix(value, 16) + .map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error)) } else if item == Self::CHAIN_VARIABLE { - let chain_id = resolver.chain_id().await?; - Ok(U256::from(chain_id)) + resolver.chain_id().await.map(U256::from) + } else if item == Self::TRANSACTION_GAS_PRICE { + context + .transaction_hash() + .context("No transaction hash provided to get the transaction gas price") + .map(|tx_hash| resolver.transaction_gas_price(tx_hash))? + .await + .map(U256::from) } else if item == Self::GAS_LIMIT_VARIABLE { - let gas_limit = resolver.block_gas_limit(BlockNumberOrTag::Latest).await?; - Ok(U256::from(gas_limit)) + resolver + .block_gas_limit(context.resolve_block_number(BlockNumberOrTag::Latest)) + .await + .map(U256::from) } else if item == Self::COINBASE_VARIABLE { - let coinbase = resolver.block_coinbase(BlockNumberOrTag::Latest).await?; - Ok(U256::from_be_slice(coinbase.as_ref())) + resolver + .block_coinbase(context.resolve_block_number(BlockNumberOrTag::Latest)) + .await + .map(|address| U256::from_be_slice(address.as_ref())) } else if item == Self::DIFFICULTY_VARIABLE { - let block_difficulty = - resolver.block_difficulty(BlockNumberOrTag::Latest).await?; - Ok(block_difficulty) + resolver + .block_difficulty(context.resolve_block_number(BlockNumberOrTag::Latest)) + .await + } else if item == Self::BLOCK_BASE_FEE_VARIABLE { + resolver + .block_base_fee(context.resolve_block_number(BlockNumberOrTag::Latest)) + .await + .map(U256::from) } else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) { let offset: u64 = item .split(':') @@ -608,31 +595,34 @@ impl> CalldataToken { .and_then(|value| value.parse().ok()) .unwrap_or_default(); - let current_block_number = resolver.last_block_number().await?; + let current_block_number = match context.tip_block_number() { + Some(block_number) => *block_number, + None => resolver.last_block_number().await?, + }; let desired_block_number = current_block_number - offset; let block_hash = resolver.block_hash(desired_block_number.into()).await?; Ok(U256::from_be_bytes(block_hash.0)) } else if item == Self::BLOCK_NUMBER_VARIABLE { - let current_block_number = resolver.last_block_number().await?; + let current_block_number = match context.tip_block_number() { + Some(block_number) => *block_number, + None => resolver.last_block_number().await?, + }; Ok(U256::from(current_block_number)) } else if item == Self::BLOCK_TIMESTAMP_VARIABLE { - let timestamp = resolver.block_timestamp(BlockNumberOrTag::Latest).await?; - Ok(U256::from(timestamp)) + resolver + .block_timestamp(BlockNumberOrTag::Latest) + .await + .map(U256::from) } else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) { - let Some(variables) = variables.into() else { - anyhow::bail!( - "Variable resolution required but no variables were passed in" - ); - }; - let Some(variable) = variables.get(variable_name) else { - anyhow::bail!("No variable found with the name {}", variable_name) - }; - Ok(*variable) + context + .variable(variable_name) + .context("Variable lookup failed") + .copied() } else { - Ok(U256::from_str_radix(item, 10) - .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))?) + U256::from_str_radix(item, 10) + .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error)) }; value.map(CalldataToken::Item) } @@ -671,50 +661,49 @@ impl<'de> Deserialize<'de> for EtherValue { mod tests { use super::*; - use alloy::json_abi::JsonAbi; - use alloy_primitives::address; + use alloy::{eips::BlockNumberOrTag, json_abi::JsonAbi}; + use alloy_primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address}; use alloy_sol_types::SolValue; use std::collections::HashMap; struct MockResolver; impl ResolverApi for MockResolver { - async fn chain_id(&self) -> anyhow::Result { + async fn chain_id(&self) -> anyhow::Result { Ok(0x123) } - async fn block_gas_limit(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result { + async fn block_gas_limit(&self, _: BlockNumberOrTag) -> anyhow::Result { Ok(0x1234) } - async fn block_coinbase( - &self, - _: alloy::eips::BlockNumberOrTag, - ) -> anyhow::Result
{ + async fn block_coinbase(&self, _: BlockNumberOrTag) -> anyhow::Result
{ Ok(Address::ZERO) } - async fn block_difficulty(&self, _: alloy::eips::BlockNumberOrTag) -> anyhow::Result { + async fn block_difficulty(&self, _: BlockNumberOrTag) -> anyhow::Result { Ok(U256::from(0x12345u128)) } - async fn block_hash( - &self, - _: alloy::eips::BlockNumberOrTag, - ) -> anyhow::Result { + async fn block_base_fee(&self, _: BlockNumberOrTag) -> anyhow::Result { + Ok(0x100) + } + + async fn block_hash(&self, _: BlockNumberOrTag) -> anyhow::Result { Ok([0xEE; 32].into()) } - async fn block_timestamp( - &self, - _: alloy::eips::BlockNumberOrTag, - ) -> anyhow::Result { + async fn block_timestamp(&self, _: BlockNumberOrTag) -> anyhow::Result { Ok(0x123456) } - async fn last_block_number(&self) -> anyhow::Result { + async fn last_block_number(&self) -> anyhow::Result { Ok(0x1234567) } + + async fn transaction_gas_price(&self, _: &TxHash) -> anyhow::Result { + Ok(0x200) + } } #[tokio::test] @@ -753,10 +742,9 @@ mod tests { (Address::ZERO, parsed_abi), ); - let encoded = input - .encoded_input(&contracts, None, &MockResolver) - .await - .unwrap(); + let resolver = MockResolver; + let context = ResolutionContext::new_from_parts(&contracts, None, None, None); + let encoded = input.encoded_input(&resolver, context).await.unwrap(); assert!(encoded.0.starts_with(&selector)); type T = (u64,); @@ -798,10 +786,9 @@ mod tests { (Address::ZERO, parsed_abi), ); - let encoded = input - .encoded_input(&contracts, None, &MockResolver) - .await - .unwrap(); + let resolver = MockResolver; + let context = ResolutionContext::new_from_parts(&contracts, None, None, None); + let encoded = input.encoded_input(&resolver, context).await.unwrap(); assert!(encoded.0.starts_with(&selector)); type T = (alloy_primitives::Address,); @@ -846,10 +833,9 @@ mod tests { (Address::ZERO, parsed_abi), ); - let encoded = input - .encoded_input(&contracts, None, &MockResolver) - .await - .unwrap(); + let resolver = MockResolver; + let context = ResolutionContext::new_from_parts(&contracts, None, None, None); + let encoded = input.encoded_input(&resolver, context).await.unwrap(); assert!(encoded.0.starts_with(&selector)); type T = (alloy_primitives::Address,); @@ -865,9 +851,8 @@ mod tests { deployed_contracts: &HashMap, resolver: &impl ResolverApi, ) -> anyhow::Result { - CalldataItem::new(input) - .resolve(deployed_contracts, None, resolver) - .await + let context = ResolutionContext::new_from_parts(deployed_contracts, None, None, None); + CalldataItem::new(input).resolve(resolver, context).await } #[tokio::test] @@ -945,6 +930,26 @@ mod tests { ) } + #[tokio::test] + async fn resolver_can_resolve_block_base_fee_variable() { + // Arrange + let input = "$BASE_FEE"; + + // Act + let resolved = resolve_calldata_item(input, &Default::default(), &MockResolver).await; + + // Assert + let resolved = resolved.expect("Failed to resolve argument"); + assert_eq!( + resolved, + MockResolver + .block_base_fee(Default::default()) + .await + .map(U256::from) + .unwrap() + ) + } + #[tokio::test] async fn resolver_can_resolve_block_hash_variable() { // Arrange diff --git a/crates/format/src/traits.rs b/crates/format/src/traits.rs index 3d302f9..057239c 100644 --- a/crates/format/src/traits.rs +++ b/crates/format/src/traits.rs @@ -1,13 +1,22 @@ +use std::collections::HashMap; + use alloy::eips::BlockNumberOrTag; +use alloy::json_abi::JsonAbi; use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256}; +use alloy_primitives::TxHash; use anyhow::Result; +use crate::metadata::ContractInstance; + /// 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) -> impl Future>; + /// Returns the gas price for the specified transaction. + fn transaction_gas_price(&self, tx_hash: &TxHash) -> impl Future>; + // 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. @@ -19,6 +28,9 @@ pub trait ResolverApi { /// Returns the difficulty of the specified block. fn block_difficulty(&self, number: BlockNumberOrTag) -> impl Future>; + /// Returns the base fee of the specified block. + fn block_base_fee(&self, number: BlockNumberOrTag) -> impl Future>; + /// Returns the hash of the specified block. fn block_hash(&self, number: BlockNumberOrTag) -> impl Future>; @@ -31,3 +43,108 @@ pub trait ResolverApi { /// Returns the number of the last block. fn last_block_number(&self) -> impl Future>; } + +#[derive(Clone, Copy, Debug, Default)] +/// Contextual information required by the code that's performing the resolution. +pub struct ResolutionContext<'a> { + /// When provided the contracts provided here will be used for resolutions. + deployed_contracts: Option<&'a HashMap>, + + /// When provided the variables in here will be used for performing resolutions. + variables: Option<&'a HashMap>, + + /// When provided this block number will be treated as the tip of the chain. + block_number: Option<&'a BlockNumber>, + + /// When provided the resolver will use this transaction hash for all of its resolutions. + transaction_hash: Option<&'a TxHash>, +} + +impl<'a> ResolutionContext<'a> { + pub fn new() -> Self { + Default::default() + } + + pub fn new_from_parts( + deployed_contracts: impl Into>>, + variables: impl Into>>, + block_number: impl Into>, + transaction_hash: impl Into>, + ) -> Self { + Self { + deployed_contracts: deployed_contracts.into(), + variables: variables.into(), + block_number: block_number.into(), + transaction_hash: transaction_hash.into(), + } + } + + pub fn with_deployed_contracts( + mut self, + deployed_contracts: impl Into>>, + ) -> Self { + self.deployed_contracts = deployed_contracts.into(); + self + } + + pub fn with_variables( + mut self, + variables: impl Into>>, + ) -> Self { + self.variables = variables.into(); + self + } + + pub fn with_block_number(mut self, block_number: impl Into>) -> Self { + self.block_number = block_number.into(); + self + } + + pub fn with_transaction_hash( + mut self, + transaction_hash: impl Into>, + ) -> Self { + self.transaction_hash = transaction_hash.into(); + self + } + + pub fn resolve_block_number(&self, number: BlockNumberOrTag) -> BlockNumberOrTag { + match self.block_number { + Some(block_number) => match number { + BlockNumberOrTag::Latest => BlockNumberOrTag::Number(*block_number), + n @ (BlockNumberOrTag::Finalized + | BlockNumberOrTag::Safe + | BlockNumberOrTag::Earliest + | BlockNumberOrTag::Pending + | BlockNumberOrTag::Number(_)) => n, + }, + None => number, + } + } + + pub fn deployed_contract(&self, instance: &ContractInstance) -> Option<&(Address, JsonAbi)> { + self.deployed_contracts + .and_then(|deployed_contracts| deployed_contracts.get(instance)) + } + + pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> { + self.deployed_contract(instance).map(|(a, _)| a) + } + + pub fn deployed_contract_abi(&self, instance: &ContractInstance) -> Option<&JsonAbi> { + self.deployed_contract(instance).map(|(_, a)| a) + } + + pub fn variable(&self, name: impl AsRef) -> Option<&U256> { + self.variables + .and_then(|variables| variables.get(name.as_ref())) + } + + pub fn tip_block_number(&self) -> Option<&'a BlockNumber> { + self.block_number + } + + pub fn transaction_hash(&self) -> Option<&'a TxHash> { + self.transaction_hash + } +} diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 9ba595a..8ad9f9c 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -17,11 +17,12 @@ use alloy::{ eips::BlockNumberOrTag, genesis::{Genesis, GenesisAccount}, network::{Ethereum, EthereumWallet, NetworkWallet}, - primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, U256}, + primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, FixedBytes, TxHash, U256}, providers::{ Provider, ProviderBuilder, ext::DebugApi, fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller}, + layers::CacheLayer, }, rpc::types::{ TransactionReceipt, TransactionRequest, @@ -29,6 +30,7 @@ use alloy::{ }, signers::local::PrivateKeySigner, }; +use anyhow::Context; use tracing::{Instrument, Level}; use revive_dt_common::{fs::clear_directory, futures::poll}; @@ -256,6 +258,7 @@ impl GethNode { .filler(FallbackGasFiller::new(500_000_000, 500_000_000, 1)) .filler(ChainIdFiller::default()) .filler(NonceFiller::new(nonce_manager)) + .layer(CacheLayer::new(10_000)) .wallet(wallet) .connect(&connection_string) .await @@ -382,6 +385,16 @@ impl ResolverApi for GethNode { .map_err(Into::into) } + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { + self.provider() + .await? + .get_transaction_receipt(*tx_hash) + .await? + .context("Failed to get the transaction receipt") + .map(|receipt| receipt.effective_gas_price) + } + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() @@ -409,7 +422,22 @@ impl ResolverApi for GethNode { .get_block_by_number(number) .await? .ok_or(anyhow::Error::msg("Blockchain has no blocks")) - .map(|block| block.header.difficulty) + .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) + } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { + self.provider() + .await? + .get_block_by_number(number) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .and_then(|block| { + block + .header + .base_fee_per_gas + .context("Failed to get the base fee per gas") + }) } #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 126b972..4b24dab 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -16,12 +16,14 @@ use alloy::{ TransactionBuilderError, UnbuiltTransactionError, }, primitives::{ - Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes, U256, + Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, FixedBytes, + TxHash, U256, }, providers::{ Provider, ProviderBuilder, ext::DebugApi, fillers::{CachedNonceManager, ChainIdFiller, FillProvider, NonceFiller, TxFiller}, + layers::CacheLayer, }, rpc::types::{ TransactionReceipt, @@ -30,6 +32,7 @@ use alloy::{ }, signers::local::PrivateKeySigner, }; +use anyhow::Context; use revive_dt_common::fs::clear_directory; use revive_dt_format::traits::ResolverApi; use serde::{Deserialize, Serialize}; @@ -370,6 +373,7 @@ impl KitchensinkNode { )) .filler(ChainIdFiller::default()) .filler(NonceFiller::new(nonce_manager)) + .layer(CacheLayer::new(10_000)) .wallet(wallet) .connect(&connection_string) .await @@ -438,6 +442,16 @@ impl ResolverApi for KitchensinkNode { .map_err(Into::into) } + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + async fn transaction_gas_price(&self, tx_hash: &TxHash) -> anyhow::Result { + self.provider() + .await? + .get_transaction_receipt(*tx_hash) + .await? + .context("Failed to get the transaction receipt") + .map(|receipt| receipt.effective_gas_price) + } + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result { self.provider() @@ -465,7 +479,22 @@ impl ResolverApi for KitchensinkNode { .get_block_by_number(number) .await? .ok_or(anyhow::Error::msg("Blockchain has no blocks")) - .map(|block| block.header.difficulty) + .map(|block| U256::from_be_bytes(block.header.mix_hash.0)) + } + + #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] + async fn block_base_fee(&self, number: BlockNumberOrTag) -> anyhow::Result { + self.provider() + .await? + .get_block_by_number(number) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .and_then(|block| { + block + .header + .base_fee_per_gas + .context("Failed to get the base fee per gas") + }) } #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]