Compare commits

...

4 Commits

Author SHA1 Message Date
Omar Abdulla aa1b5621dd Use SolidityLang for solc downloads 2025-08-06 11:46:23 +03:00
Omar 9980926d40 Add a case ignore flag (#114)
* Added a resolver tied to a specific block

* Increase the number of private keys

* Increase kitchensink wait time to 60 seconds

* Add a case ignore flag
2025-08-04 16:40:53 +00:00
Omar ff993d44a5 Added a resolver tied to a specific block (#111)
* Added a resolver tied to a specific block

* Increase the number of private keys

* Increase kitchensink wait time to 60 seconds
2025-08-04 12:45:47 +00:00
Omar 8cbb1a9f77 Added basic console reporting (#110)
* Added basic console reporting

* Add some waiting period to the printing task

* Print to the stderr and print logs to stdout
2025-08-04 06:05:49 +00:00
10 changed files with 412 additions and 156 deletions
+21
View File
@@ -238,4 +238,25 @@ mod test {
Version::new(0, 7, 6) Version::new(0, 7, 6)
) )
} }
#[tokio::test]
async fn compiler_version_can_be_obtained1() {
// Arrange
let args = Arguments::default();
println!("Getting compiler path");
let path = Solc::get_compiler_executable(&args, Version::new(0, 4, 21))
.await
.unwrap();
println!("Got compiler path");
let compiler = Solc::new(path);
// Act
let version = compiler.version();
// Assert
assert_eq!(
version.expect("Failed to get version"),
Version::new(0, 4, 21)
)
}
} }
+1 -1
View File
@@ -77,7 +77,7 @@ pub struct Arguments {
/// This argument controls which private keys the nodes should have access to and be added to /// This argument controls which private keys the nodes should have access to and be added to
/// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set /// its wallet signers. With a value of N, private keys (0, N] will be added to the signer set
/// of the node. /// of the node.
#[arg(long = "private-keys-count", default_value_t = 15_000)] #[arg(long = "private-keys-count", default_value_t = 100_000)]
pub private_keys_to_add: usize, pub private_keys_to_add: usize,
/// The differential testing leader node implementation. /// The differential testing leader node implementation.
+97 -28
View File
@@ -4,9 +4,11 @@ use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::PathBuf; use std::path::PathBuf;
use alloy::eips::BlockNumberOrTag;
use alloy::hex;
use alloy::json_abi::JsonAbi; use alloy::json_abi::JsonAbi;
use alloy::network::{Ethereum, TransactionBuilder}; use alloy::network::{Ethereum, TransactionBuilder};
use alloy::primitives::U256; use alloy::primitives::{BlockNumber, U256};
use alloy::rpc::types::TransactionReceipt; use alloy::rpc::types::TransactionReceipt;
use alloy::rpc::types::trace::geth::{ use alloy::rpc::types::trace::geth::{
CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace, CallFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTrace,
@@ -21,6 +23,7 @@ use alloy::{
}; };
use anyhow::Context; use anyhow::Context;
use indexmap::IndexMap; use indexmap::IndexMap;
use revive_dt_format::traits::ResolverApi;
use semver::Version; use semver::Version;
use revive_dt_format::case::{Case, CaseIdx}; use revive_dt_format::case::{Case, CaseIdx};
@@ -29,6 +32,7 @@ use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdent};
use revive_dt_format::{input::Input, metadata::Metadata}; use revive_dt_format::{input::Input, metadata::Metadata};
use revive_dt_node::Node; use revive_dt_node::Node;
use revive_dt_node_interaction::EthereumNode; use revive_dt_node_interaction::EthereumNode;
use tracing::Instrument;
use crate::Platform; use crate::Platform;
@@ -84,7 +88,13 @@ where
.handle_input_call_frame_tracing(&execution_receipt, node) .handle_input_call_frame_tracing(&execution_receipt, node)
.await?; .await?;
self.handle_input_variable_assignment(input, &tracing_result)?; self.handle_input_variable_assignment(input, &tracing_result)?;
self.handle_input_expectations(input, &execution_receipt, node, &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)
.await?; .await?;
self.handle_input_diff(case_idx, execution_receipt, node) self.handle_input_diff(case_idx, execution_receipt, node)
.await .await
@@ -233,6 +243,11 @@ where
) { ) {
let value = U256::from_be_slice(output_word); let value = U256::from_be_slice(output_word);
self.variables.insert(variable_name.clone(), value); self.variables.insert(variable_name.clone(), value);
tracing::info!(
variable_name,
variable_value = hex::encode(value.to_be_bytes::<32>()),
"Assigned variable"
);
} }
Ok(()) Ok(())
@@ -242,7 +257,7 @@ where
&mut self, &mut self,
input: &Input, input: &Input,
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
node: &T::Blockchain, resolver: &impl ResolverApi,
tracing_result: &CallFrame, tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let span = tracing::info_span!("Handling input expectations"); let span = tracing::info_span!("Handling input expectations");
@@ -279,7 +294,7 @@ where
for expectation in expectations.iter() { for expectation in expectations.iter() {
self.handle_input_expectation_item( self.handle_input_expectation_item(
execution_receipt, execution_receipt,
node, resolver,
expectation, expectation,
tracing_result, tracing_result,
) )
@@ -292,7 +307,7 @@ where
async fn handle_input_expectation_item( async fn handle_input_expectation_item(
&mut self, &mut self,
execution_receipt: &TransactionReceipt, execution_receipt: &TransactionReceipt,
node: &T::Blockchain, resolver: &impl ResolverApi,
expectation: &ExpectedOutput, expectation: &ExpectedOutput,
tracing_result: &CallFrame, tracing_result: &CallFrame,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@@ -304,7 +319,6 @@ where
let deployed_contracts = &mut self.deployed_contracts; let deployed_contracts = &mut self.deployed_contracts;
let variables = &mut self.variables; let variables = &mut self.variables;
let chain_state_provider = node;
// Handling the receipt state assertion. // Handling the receipt state assertion.
let expected = !expectation.exception; let expected = !expectation.exception;
@@ -327,12 +341,7 @@ where
let expected = expected_calldata; let expected = expected_calldata;
let actual = &tracing_result.output.as_ref().unwrap_or_default(); let actual = &tracing_result.output.as_ref().unwrap_or_default();
if !expected if !expected
.is_equivalent( .is_equivalent(actual, deployed_contracts, &*variables, resolver)
actual,
deployed_contracts,
&*variables,
chain_state_provider,
)
.await? .await?
{ {
tracing::error!( tracing::error!(
@@ -358,14 +367,16 @@ where
} }
// Handling the events assertion. // Handling the events assertion.
for (expected_event, actual_event) in for (event_idx, (expected_event, actual_event)) in expected_events
expected_events.iter().zip(execution_receipt.logs()) .iter()
.zip(execution_receipt.logs())
.enumerate()
{ {
// Handling the emitter assertion. // Handling the emitter assertion.
if let Some(ref expected_address) = expected_event.address { if let Some(ref expected_address) = expected_event.address {
let expected = Address::from_slice( let expected = Address::from_slice(
Calldata::new_compound([expected_address]) Calldata::new_compound([expected_address])
.calldata(deployed_contracts, &*variables, node) .calldata(deployed_contracts, &*variables, resolver)
.await? .await?
.get(12..32) .get(12..32)
.expect("Can't fail"), .expect("Can't fail"),
@@ -373,6 +384,7 @@ where
let actual = actual_event.address(); let actual = actual_event.address();
if actual != expected { if actual != expected {
tracing::error!( tracing::error!(
event_idx,
%expected, %expected,
%actual, %actual,
"Event emitter assertion failed", "Event emitter assertion failed",
@@ -392,15 +404,11 @@ where
{ {
let expected = Calldata::new_compound([expected]); let expected = Calldata::new_compound([expected]);
if !expected if !expected
.is_equivalent( .is_equivalent(&actual.0, deployed_contracts, &*variables, resolver)
&actual.0,
deployed_contracts,
&*variables,
chain_state_provider,
)
.await? .await?
{ {
tracing::error!( tracing::error!(
event_idx,
?execution_receipt, ?execution_receipt,
?expected, ?expected,
?actual, ?actual,
@@ -416,15 +424,11 @@ where
let expected = &expected_event.values; let expected = &expected_event.values;
let actual = &actual_event.data().data; let actual = &actual_event.data().data;
if !expected if !expected
.is_equivalent( .is_equivalent(&actual.0, deployed_contracts, &*variables, resolver)
&actual.0,
deployed_contracts,
&*variables,
chain_state_provider,
)
.await? .await?
{ {
tracing::error!( tracing::error!(
event_idx,
?execution_receipt, ?execution_receipt,
?expected, ?expected,
?actual, ?actual,
@@ -649,15 +653,16 @@ where
let mut inputs_executed = 0; let mut inputs_executed = 0;
for (input_idx, input) in self.case.inputs_iterator().enumerate() { for (input_idx, input) in self.case.inputs_iterator().enumerate() {
let tracing_span = tracing::info_span!("Handling input", input_idx); let tracing_span = tracing::info_span!("Handling input", input_idx);
let _guard = tracing_span.enter();
let (leader_receipt, _, leader_diff) = self let (leader_receipt, _, leader_diff) = self
.leader_state .leader_state
.handle_input(self.metadata, self.case_idx, &input, self.leader_node) .handle_input(self.metadata, self.case_idx, &input, self.leader_node)
.instrument(tracing_span.clone())
.await?; .await?;
let (follower_receipt, _, follower_diff) = self let (follower_receipt, _, follower_diff) = self
.follower_state .follower_state
.handle_input(self.metadata, self.case_idx, &input, self.follower_node) .handle_input(self.metadata, self.case_idx, &input, self.follower_node)
.instrument(tracing_span)
.await?; .await?;
if leader_diff == follower_diff { if leader_diff == follower_diff {
@@ -680,3 +685,67 @@ where
Ok(inputs_executed) 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<alloy::primitives::ChainId> {
self.node.chain_id().await
}
async fn block_gas_limit(&self, number: BlockNumberOrTag) -> anyhow::Result<u128> {
self.node
.block_gas_limit(self.resolve_block_number_or_tag(number))
.await
}
async fn block_coinbase(&self, number: BlockNumberOrTag) -> anyhow::Result<Address> {
self.node
.block_coinbase(self.resolve_block_number_or_tag(number))
.await
}
async fn block_difficulty(&self, number: BlockNumberOrTag) -> anyhow::Result<U256> {
self.node
.block_difficulty(self.resolve_block_number_or_tag(number))
.await
}
async fn block_hash(
&self,
number: BlockNumberOrTag,
) -> anyhow::Result<alloy::primitives::BlockHash> {
self.node
.block_hash(self.resolve_block_number_or_tag(number))
.await
}
async fn block_timestamp(
&self,
number: BlockNumberOrTag,
) -> anyhow::Result<alloy::primitives::BlockTimestamp> {
self.node
.block_timestamp(self.resolve_block_number_or_tag(number))
.await
}
async fn last_block_number(&self) -> anyhow::Result<alloy::primitives::BlockNumber> {
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,
}
}
}
+198 -15
View File
@@ -150,11 +150,145 @@ where
}) })
}, },
) )
.filter(
|(metadata_file_path, metadata, _, _, _)| match metadata.ignore {
Some(true) => {
tracing::warn!(
metadata_file_path = %metadata_file_path.display(),
"Ignoring metadata file"
);
false
}
Some(false) | None => true,
},
)
.filter(
|(metadata_file_path, _, case_idx, case, _)| match case.ignore {
Some(true) => {
tracing::warn!(
metadata_file_path = %metadata_file_path.display(),
case_idx,
case_name = ?case.name,
"Ignoring case"
);
false
}
Some(false) | None => true,
},
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let metadata_case_status = Arc::new(RwLock::new(test_cases.iter().fold(
HashMap::<_, HashMap<_, _>>::new(),
|mut map, (path, _, case_idx, case, solc_mode)| {
map.entry((path.to_path_buf(), solc_mode.clone()))
.or_default()
.insert((CaseIdx::new(*case_idx), case.name.clone()), None::<bool>);
map
},
)));
let status_reporter_task = {
let metadata_case_status = metadata_case_status.clone();
async move {
const GREEN: &str = "\x1B[32m";
const RED: &str = "\x1B[31m";
const RESET: &str = "\x1B[0m";
let mut entries_to_delete = Vec::new();
let mut number_of_successes = 0;
let mut number_of_failures = 0;
loop {
let metadata_case_status_read = metadata_case_status.read().await;
if metadata_case_status_read.is_empty() {
break;
}
for ((metadata_file_path, solc_mode), case_status) in
metadata_case_status_read.iter()
{
if case_status.values().any(|value| value.is_none()) {
continue;
}
let contains_failures = case_status
.values()
.any(|value| value.is_some_and(|value| !value));
if !contains_failures {
eprintln!(
"{}Succeeded:{} {} - {:?}",
GREEN,
RESET,
metadata_file_path.display(),
solc_mode
)
} else {
eprintln!(
"{}Failed:{} {} - {:?}",
RED,
RESET,
metadata_file_path.display(),
solc_mode
)
};
number_of_successes += case_status
.values()
.filter(|value| value.is_some_and(|value| value))
.count();
number_of_failures += case_status
.values()
.filter(|value| value.is_some_and(|value| !value))
.count();
let mut case_status = case_status
.iter()
.map(|((case_idx, case_name), case_status)| {
(case_idx.into_inner(), case_name, case_status.unwrap())
})
.collect::<Vec<_>>();
case_status.sort_by(|a, b| a.0.cmp(&b.0));
for (_, case_name, case_status) in case_status.into_iter() {
if case_status {
eprintln!(
"{GREEN} Case Succeeded:{RESET} {}",
case_name
.as_ref()
.map(|string| string.as_str())
.unwrap_or("Unnamed case")
)
} else {
eprintln!(
"{RED} Case Failed:{RESET} {}",
case_name
.as_ref()
.map(|string| string.as_str())
.unwrap_or("Unnamed case")
)
};
}
eprintln!();
entries_to_delete.push((metadata_file_path.clone(), solc_mode.clone()));
}
drop(metadata_case_status_read);
let mut metadata_case_status_write = metadata_case_status.write().await;
for entry in entries_to_delete.drain(..) {
metadata_case_status_write.remove(&entry);
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}
eprintln!(
"{GREEN}{number_of_successes}{RESET} cases succeeded, {RED}{number_of_failures}{RESET} cases failed"
);
}
};
let compilation_cache = Arc::new(RwLock::new(HashMap::new())); let compilation_cache = Arc::new(RwLock::new(HashMap::new()));
futures::stream::iter(test_cases) let driver_task = futures::stream::iter(test_cases).for_each_concurrent(
.for_each_concurrent(
None, None,
|(metadata_file_path, metadata, case_idx, case, solc_mode)| { |(metadata_file_path, metadata, case_idx, case, solc_mode)| {
let compilation_cache = compilation_cache.clone(); let compilation_cache = compilation_cache.clone();
@@ -167,13 +301,14 @@ where
case_idx = case_idx, case_idx = case_idx,
solc_mode = ?solc_mode, solc_mode = ?solc_mode,
); );
let metadata_case_status = metadata_case_status.clone();
async move { async move {
let result = handle_case_driver::<L, F>( let result = handle_case_driver::<L, F>(
metadata_file_path.as_path(), metadata_file_path.as_path(),
metadata, metadata,
case_idx.into(), case_idx.into(),
case, case,
solc_mode, solc_mode.clone(),
args, args,
compilation_cache.clone(), compilation_cache.clone(),
leader_node, leader_node,
@@ -181,18 +316,30 @@ where
span, span,
) )
.await; .await;
let mut metadata_case_status = metadata_case_status.write().await;
match result { match result {
Ok(inputs_executed) => { Ok(inputs_executed) => {
tracing::info!(inputs_executed, "Execution succeeded") tracing::info!(inputs_executed, "Execution succeeded");
metadata_case_status
.entry((metadata_file_path.clone(), solc_mode))
.or_default()
.insert((CaseIdx::new(case_idx), case.name.clone()), Some(true));
}
Err(error) => {
metadata_case_status
.entry((metadata_file_path.clone(), solc_mode))
.or_default()
.insert((CaseIdx::new(case_idx), case.name.clone()), Some(false));
tracing::error!(%error, "Execution failed")
} }
Err(error) => tracing::info!(%error, "Execution failed"),
} }
tracing::info!("Execution completed"); tracing::info!("Execution completed");
} }
.instrument(tracing_span) .instrument(tracing_span)
}, },
) );
.await;
tokio::join!(status_reporter_task, driver_task);
Ok(()) Ok(())
} }
@@ -330,6 +477,17 @@ where
} }
}; };
tracing::info!(
?library_instance,
library_address = ?leader_receipt.contract_address,
"Deployed library to leader"
);
tracing::info!(
?library_instance,
library_address = ?follower_receipt.contract_address,
"Deployed library to follower"
);
let Some(leader_library_address) = leader_receipt.contract_address else { let Some(leader_library_address) = leader_receipt.contract_address else {
tracing::error!("Contract deployment transaction didn't return an address"); tracing::error!("Contract deployment transaction didn't return an address");
anyhow::bail!("Contract deployment didn't return an address"); anyhow::bail!("Contract deployment didn't return an address");
@@ -439,7 +597,14 @@ async fn get_or_build_contracts<'a, P: Platform>(
None => { None => {
tracing::debug!(?key, "Compiled contracts cache miss"); tracing::debug!(?key, "Compiled contracts cache miss");
let compiled_contracts = Arc::new( let compiled_contracts = Arc::new(
compile_contracts::<P>(metadata, &mode, config, deployed_libraries).await?, compile_contracts::<P>(
metadata,
metadata_file_path,
&mode,
config,
deployed_libraries,
)
.await?,
); );
*compilation_artifact = Some(compiled_contracts.clone()); *compilation_artifact = Some(compiled_contracts.clone());
return Ok(compiled_contracts.clone()); return Ok(compiled_contracts.clone());
@@ -455,14 +620,23 @@ async fn get_or_build_contracts<'a, P: Platform>(
mutex mutex
}; };
let mut compilation_artifact = mutex.lock().await; let mut compilation_artifact = mutex.lock().await;
let compiled_contracts = let compiled_contracts = Arc::new(
Arc::new(compile_contracts::<P>(metadata, &mode, config, deployed_libraries).await?); compile_contracts::<P>(
metadata,
metadata_file_path,
&mode,
config,
deployed_libraries,
)
.await?,
);
*compilation_artifact = Some(compiled_contracts.clone()); *compilation_artifact = Some(compiled_contracts.clone());
Ok(compiled_contracts.clone()) Ok(compiled_contracts.clone())
} }
async fn compile_contracts<P: Platform>( async fn compile_contracts<P: Platform>(
metadata: &Metadata, metadata: &Metadata,
metadata_file_path: &Path,
mode: &SolcMode, mode: &SolcMode,
config: &Arguments, config: &Arguments,
deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_libraries: &HashMap<ContractInstance, (Address, JsonAbi)>,
@@ -472,6 +646,13 @@ async fn compile_contracts<P: Platform>(
P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?; P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?;
let compiler_version = P::Compiler::new(compiler_path.clone()).version()?; let compiler_version = P::Compiler::new(compiler_path.clone()).version()?;
tracing::info!(
%compiler_version,
metadata_file_path = %metadata_file_path.display(),
mode = ?mode,
"Compiling contracts"
);
let compiler = Compiler::<P::Compiler>::new() let compiler = Compiler::<P::Compiler>::new()
.with_allow_path(metadata.directory()?) .with_allow_path(metadata.directory()?)
.with_optimization(mode.solc_optimize()); .with_optimization(mode.solc_optimize());
@@ -486,11 +667,11 @@ async fn compile_contracts<P: Platform>(
.expect("Impossible for library to not be found in contracts") .expect("Impossible for library to not be found in contracts")
.contract_ident; .contract_ident;
// Note the following: we need to tell solc which files require the libraries to be // Note the following: we need to tell solc which files require the libraries to be linked
// linked into them. We do not have access to this information and therefore we choose // into them. We do not have access to this information and therefore we choose an easier,
// an easier, yet more compute intensive route, of telling solc that all of the files // yet more compute intensive route, of telling solc that all of the files need to link the
// need to link the library and it will only perform the linking for the files that do // library and it will only perform the linking for the files that do actually need the
// actually need the library. // library.
compiler = FilesWithExtensionIterator::new(metadata.directory()?) compiler = FilesWithExtensionIterator::new(metadata.directory()?)
.with_allowed_extension("sol") .with_allowed_extension("sol")
.fold(compiler, |compiler, path| { .fold(compiler, |compiler, path| {
@@ -540,6 +721,7 @@ async fn compile_corpus(
TestingPlatform::Geth => { TestingPlatform::Geth => {
let _ = compile_contracts::<Geth>( let _ = compile_contracts::<Geth>(
&metadata.content, &metadata.content,
&metadata.path,
&mode, &mode,
config, config,
&Default::default(), &Default::default(),
@@ -549,6 +731,7 @@ async fn compile_corpus(
TestingPlatform::Kitchensink => { TestingPlatform::Kitchensink => {
let _ = compile_contracts::<Geth>( let _ = compile_contracts::<Geth>(
&metadata.content, &metadata.content,
&metadata.path,
&mode, &mode,
config, config,
&Default::default(), &Default::default(),
+1
View File
@@ -15,6 +15,7 @@ pub struct Case {
pub inputs: Vec<Input>, pub inputs: Vec<Input>,
pub group: Option<String>, pub group: Option<String>,
pub expected: Option<Expected>, pub expected: Option<Expected>,
pub ignore: Option<bool>,
} }
impl Case { impl Case {
+25 -44
View File
@@ -202,13 +202,13 @@ impl Input {
&'a self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<Bytes> { ) -> anyhow::Result<Bytes> {
match self.method { match self.method {
Method::Deployer | Method::Fallback => { Method::Deployer | Method::Fallback => {
let calldata = self let calldata = self
.calldata .calldata
.calldata(deployed_contracts, variables, chain_state_provider) .calldata(deployed_contracts, variables, resolver)
.await?; .await?;
Ok(calldata.into()) Ok(calldata.into())
@@ -256,12 +256,7 @@ impl Input {
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement()); let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
calldata.extend(function.selector().0); calldata.extend(function.selector().0);
self.calldata self.calldata
.calldata_into_slice( .calldata_into_slice(&mut calldata, deployed_contracts, variables, resolver)
&mut calldata,
deployed_contracts,
variables,
chain_state_provider,
)
.await?; .await?;
Ok(calldata.into()) Ok(calldata.into())
@@ -274,10 +269,10 @@ impl Input {
&'a self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<TransactionRequest> { ) -> anyhow::Result<TransactionRequest> {
let input_data = self let input_data = self
.encoded_input(deployed_contracts, variables, chain_state_provider) .encoded_input(deployed_contracts, variables, resolver)
.await?; .await?;
let transaction_request = TransactionRequest::default().from(self.caller).value( let transaction_request = TransactionRequest::default().from(self.caller).value(
self.value self.value
@@ -360,15 +355,10 @@ impl Calldata {
&'a self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement()); let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice( self.calldata_into_slice(&mut buffer, deployed_contracts, variables, resolver)
&mut buffer,
deployed_contracts,
variables,
chain_state_provider,
)
.await?; .await?;
Ok(buffer) Ok(buffer)
} }
@@ -378,7 +368,7 @@ impl Calldata {
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
Calldata::Single(bytes) => { Calldata::Single(bytes) => {
@@ -387,7 +377,7 @@ impl Calldata {
Calldata::Compound(items) => { Calldata::Compound(items) => {
for (arg_idx, arg) in items.iter().enumerate() { for (arg_idx, arg) in items.iter().enumerate() {
match arg match arg
.resolve(deployed_contracts, variables.clone(), chain_state_provider) .resolve(deployed_contracts, variables.clone(), resolver)
.await .await
{ {
Ok(resolved) => { Ok(resolved) => {
@@ -417,7 +407,7 @@ impl Calldata {
other: &[u8], other: &[u8],
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
match self { match self {
Calldata::Single(calldata) => Ok(calldata == other), Calldata::Single(calldata) => Ok(calldata == other),
@@ -440,7 +430,7 @@ impl Calldata {
}; };
let this = this let this = this
.resolve(deployed_contracts, variables.clone(), chain_state_provider) .resolve(deployed_contracts, variables.clone(), resolver)
.await?; .await?;
let other = U256::from_be_slice(&other); let other = U256::from_be_slice(&other);
if this != other { if this != other {
@@ -458,13 +448,13 @@ impl CalldataItem {
&'a self, &'a self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new(); let mut stack = Vec::<CalldataToken<U256>>::new();
for token in self for token in self
.calldata_tokens() .calldata_tokens()
.map(|token| token.resolve(deployed_contracts, variables.clone(), chain_state_provider)) .map(|token| token.resolve(deployed_contracts, variables.clone(), resolver))
{ {
let token = token.await?; let token = token.await?;
let new_token = match token { let new_token = match token {
@@ -569,7 +559,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
self, self,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone, variables: impl Into<Option<&'a HashMap<String, U256>>> + Clone,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<CalldataToken<U256>> { ) -> anyhow::Result<CalldataToken<U256>> {
match self { match self {
Self::Item(item) => { Self::Item(item) => {
@@ -599,22 +589,17 @@ impl<T: AsRef<str>> CalldataToken<T> {
anyhow::anyhow!("Invalid hexadecimal literal: {}", error) anyhow::anyhow!("Invalid hexadecimal literal: {}", error)
})?) })?)
} else if item == Self::CHAIN_VARIABLE { } else if item == Self::CHAIN_VARIABLE {
let chain_id = chain_state_provider.chain_id().await?; let chain_id = resolver.chain_id().await?;
Ok(U256::from(chain_id)) Ok(U256::from(chain_id))
} else if item == Self::GAS_LIMIT_VARIABLE { } else if item == Self::GAS_LIMIT_VARIABLE {
let gas_limit = chain_state_provider let gas_limit = resolver.block_gas_limit(BlockNumberOrTag::Latest).await?;
.block_gas_limit(BlockNumberOrTag::Latest)
.await?;
Ok(U256::from(gas_limit)) Ok(U256::from(gas_limit))
} else if item == Self::COINBASE_VARIABLE { } else if item == Self::COINBASE_VARIABLE {
let coinbase = chain_state_provider let coinbase = resolver.block_coinbase(BlockNumberOrTag::Latest).await?;
.block_coinbase(BlockNumberOrTag::Latest)
.await?;
Ok(U256::from_be_slice(coinbase.as_ref())) Ok(U256::from_be_slice(coinbase.as_ref()))
} else if item == Self::DIFFICULTY_VARIABLE { } else if item == Self::DIFFICULTY_VARIABLE {
let block_difficulty = chain_state_provider let block_difficulty =
.block_difficulty(BlockNumberOrTag::Latest) resolver.block_difficulty(BlockNumberOrTag::Latest).await?;
.await?;
Ok(block_difficulty) Ok(block_difficulty)
} else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) { } else if item.starts_with(Self::BLOCK_HASH_VARIABLE_PREFIX) {
let offset: u64 = item let offset: u64 = item
@@ -623,21 +608,17 @@ impl<T: AsRef<str>> CalldataToken<T> {
.and_then(|value| value.parse().ok()) .and_then(|value| value.parse().ok())
.unwrap_or_default(); .unwrap_or_default();
let current_block_number = chain_state_provider.last_block_number().await?; let current_block_number = resolver.last_block_number().await?;
let desired_block_number = current_block_number - offset; let desired_block_number = current_block_number - offset;
let block_hash = chain_state_provider let block_hash = resolver.block_hash(desired_block_number.into()).await?;
.block_hash(desired_block_number.into())
.await?;
Ok(U256::from_be_bytes(block_hash.0)) Ok(U256::from_be_bytes(block_hash.0))
} else if item == Self::BLOCK_NUMBER_VARIABLE { } else if item == Self::BLOCK_NUMBER_VARIABLE {
let current_block_number = chain_state_provider.last_block_number().await?; let current_block_number = resolver.last_block_number().await?;
Ok(U256::from(current_block_number)) Ok(U256::from(current_block_number))
} else if item == Self::BLOCK_TIMESTAMP_VARIABLE { } else if item == Self::BLOCK_TIMESTAMP_VARIABLE {
let timestamp = chain_state_provider let timestamp = resolver.block_timestamp(BlockNumberOrTag::Latest).await?;
.block_timestamp(BlockNumberOrTag::Latest)
.await?;
Ok(U256::from(timestamp)) Ok(U256::from(timestamp))
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) { } else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
let Some(variables) = variables.into() else { let Some(variables) = variables.into() else {
@@ -882,10 +863,10 @@ mod tests {
async fn resolve_calldata_item( async fn resolve_calldata_item(
input: &str, input: &str,
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>, deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
chain_state_provider: &impl ResolverApi, resolver: &impl ResolverApi,
) -> anyhow::Result<U256> { ) -> anyhow::Result<U256> {
CalldataItem::new(input) CalldataItem::new(input)
.resolve(deployed_contracts, None, chain_state_provider) .resolve(deployed_contracts, None, resolver)
.await .await
} }
+2 -2
View File
@@ -209,7 +209,7 @@ impl KitchensinkNode {
if let Err(error) = Self::wait_ready( if let Err(error) = Self::wait_ready(
self.kitchensink_stderr_log_file_path().as_path(), self.kitchensink_stderr_log_file_path().as_path(),
Self::SUBSTRATE_READY_MARKER, Self::SUBSTRATE_READY_MARKER,
Duration::from_secs(30), Duration::from_secs(60),
) { ) {
tracing::error!( tracing::error!(
?error, ?error,
@@ -238,7 +238,7 @@ impl KitchensinkNode {
if let Err(error) = Self::wait_ready( if let Err(error) = Self::wait_ready(
self.proxy_stderr_log_file_path().as_path(), self.proxy_stderr_log_file_path().as_path(),
Self::ETH_PROXY_READY_MARKER, Self::ETH_PROXY_READY_MARKER,
Duration::from_secs(30), Duration::from_secs(60),
) { ) {
tracing::error!(?error, "Failed to start proxy, shutting down gracefully"); tracing::error!(?error, "Failed to start proxy, shutting down gracefully");
self.shutdown()?; self.shutdown()?;
+3 -3
View File
@@ -11,14 +11,14 @@ use std::{
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::download::GHDownloader; use crate::download::SolcDownloader;
pub const SOLC_CACHE_DIRECTORY: &str = "solc"; pub const SOLC_CACHE_DIRECTORY: &str = "solc";
pub(crate) static SOLC_CACHER: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default); pub(crate) static SOLC_CACHER: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
pub(crate) async fn get_or_download( pub(crate) async fn get_or_download(
working_directory: &Path, working_directory: &Path,
downloader: &GHDownloader, downloader: &SolcDownloader,
) -> anyhow::Result<PathBuf> { ) -> anyhow::Result<PathBuf> {
let target_directory = working_directory let target_directory = working_directory
.join(SOLC_CACHE_DIRECTORY) .join(SOLC_CACHE_DIRECTORY)
@@ -38,7 +38,7 @@ pub(crate) async fn get_or_download(
Ok(target_file) Ok(target_file)
} }
async fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<()> { async fn download_to_file(path: &Path, downloader: &SolcDownloader) -> anyhow::Result<()> {
tracing::info!("caching file: {}", path.display()); tracing::info!("caching file: {}", path.display());
let Ok(file) = File::create_new(path) else { let Ok(file) = File::create_new(path) else {
+25 -24
View File
@@ -38,21 +38,21 @@ impl List {
} }
} }
/// Download solc binaries from GitHub releases (IPFS links aren't reliable). /// Download solc binaries from the official SolidityLang site
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GHDownloader { pub struct SolcDownloader {
pub version: Version, pub version: Version,
pub target: &'static str, pub target: &'static str,
pub list: &'static str, pub list: &'static str,
} }
impl GHDownloader { impl SolcDownloader {
pub const BASE_URL: &str = "https://github.com/ethereum/solidity/releases/download"; pub const BASE_URL: &str = "https://binaries.soliditylang.org";
pub const LINUX_NAME: &str = "solc-static-linux"; pub const LINUX_NAME: &str = "linux-amd64";
pub const MACOSX_NAME: &str = "solc-macos"; pub const MACOSX_NAME: &str = "macosx-amd64";
pub const WINDOWS_NAME: &str = "solc-windows.exe"; pub const WINDOWS_NAME: &str = "windows-amd64";
pub const WASM_NAME: &str = "soljson.js"; pub const WASM_NAME: &str = "wasm";
async fn new( async fn new(
version: impl Into<VersionOrRequirement>, version: impl Into<VersionOrRequirement>,
@@ -102,26 +102,27 @@ impl GHDownloader {
Self::new(version, Self::WASM_NAME, List::WASM_URL).await Self::new(version, Self::WASM_NAME, List::WASM_URL).await
} }
/// Returns the download link.
pub fn url(&self) -> String {
format!("{}/v{}/{}", Self::BASE_URL, &self.version, &self.target)
}
/// Download the solc binary. /// Download the solc binary.
/// ///
/// Errors out if the download fails or the digest of the downloaded file /// Errors out if the download fails or the digest of the downloaded file
/// mismatches the expected digest from the release [List]. /// mismatches the expected digest from the release [List].
pub async fn download(&self) -> anyhow::Result<Vec<u8>> { pub async fn download(&self) -> anyhow::Result<Vec<u8>> {
tracing::info!("downloading solc: {self:?}"); tracing::info!("downloading solc: {self:?}");
let expected_digest = List::download(self.list) let builds = List::download(self.list).await?.builds;
.await? let build = builds
.builds
.iter() .iter()
.find(|build| build.version == self.version) .find(|build| build.version == self.version)
.ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version)) .ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version))?;
.map(|b| b.sha256.strip_prefix("0x").unwrap_or(&b.sha256).to_string())?;
let file = reqwest::get(self.url()).await?.bytes().await?.to_vec(); let path = build.path.clone();
let expected_digest = build
.sha256
.strip_prefix("0x")
.unwrap_or(&build.sha256)
.to_string();
let url = format!("{}/{}/{}", Self::BASE_URL, self.target, path.display());
let file = reqwest::get(url).await?.bytes().await?.to_vec();
if hex::encode(Sha256::digest(&file)) != expected_digest { if hex::encode(Sha256::digest(&file)) != expected_digest {
anyhow::bail!("sha256 mismatch for solc version {}", self.version); anyhow::bail!("sha256 mismatch for solc version {}", self.version);
@@ -133,7 +134,7 @@ impl GHDownloader {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{download::GHDownloader, list::List}; use crate::{download::SolcDownloader, list::List};
#[tokio::test] #[tokio::test]
async fn try_get_windows() { async fn try_get_windows() {
@@ -141,7 +142,7 @@ mod tests {
.await .await
.unwrap() .unwrap()
.latest_release; .latest_release;
GHDownloader::windows(version) SolcDownloader::windows(version)
.await .await
.unwrap() .unwrap()
.download() .download()
@@ -155,7 +156,7 @@ mod tests {
.await .await
.unwrap() .unwrap()
.latest_release; .latest_release;
GHDownloader::macosx(version) SolcDownloader::macosx(version)
.await .await
.unwrap() .unwrap()
.download() .download()
@@ -169,7 +170,7 @@ mod tests {
.await .await
.unwrap() .unwrap()
.latest_release; .latest_release;
GHDownloader::linux(version) SolcDownloader::linux(version)
.await .await
.unwrap() .unwrap()
.download() .download()
@@ -180,7 +181,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn try_get_wasm() { async fn try_get_wasm() {
let version = List::download(List::WASM_URL).await.unwrap().latest_release; let version = List::download(List::WASM_URL).await.unwrap().latest_release;
GHDownloader::wasm(version) SolcDownloader::wasm(version)
.await .await
.unwrap() .unwrap()
.download() .download()
+5 -5
View File
@@ -6,7 +6,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use cache::get_or_download; use cache::get_or_download;
use download::GHDownloader; use download::SolcDownloader;
use revive_dt_common::types::VersionOrRequirement; use revive_dt_common::types::VersionOrRequirement;
@@ -25,13 +25,13 @@ pub async fn download_solc(
wasm: bool, wasm: bool,
) -> anyhow::Result<PathBuf> { ) -> anyhow::Result<PathBuf> {
let downloader = if wasm { let downloader = if wasm {
GHDownloader::wasm(version).await SolcDownloader::wasm(version).await
} else if cfg!(target_os = "linux") { } else if cfg!(target_os = "linux") {
GHDownloader::linux(version).await SolcDownloader::linux(version).await
} else if cfg!(target_os = "macos") { } else if cfg!(target_os = "macos") {
GHDownloader::macosx(version).await SolcDownloader::macosx(version).await
} else if cfg!(target_os = "windows") { } else if cfg!(target_os = "windows") {
GHDownloader::windows(version).await SolcDownloader::windows(version).await
} else { } else {
unimplemented!() unimplemented!()
}?; }?;