mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-22 21:57:58 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8017b4ddf | |||
| eacc09fe4f | |||
| 7ef76bb470 |
Generated
+1
@@ -3963,6 +3963,7 @@ dependencies = [
|
|||||||
name = "revive-dt-compiler"
|
name = "revive-dt-compiler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"alloy-primitives",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"revive-common",
|
"revive-common",
|
||||||
"revive-dt-config",
|
"revive-dt-config",
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
|
||||||
revive-solc-json-interface = { workspace = true }
|
revive-solc-json-interface = { workspace = true }
|
||||||
revive-dt-config = { workspace = true }
|
revive-dt-config = { workspace = true }
|
||||||
revive-dt-solc-binaries = { workspace = true }
|
revive-dt-solc-binaries = { workspace = true }
|
||||||
revive-common = { workspace = true }
|
revive-common = { workspace = true }
|
||||||
|
|
||||||
|
alloy-primitives = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use alloy_primitives::Address;
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
|
|
||||||
use revive_common::EVMVersion;
|
use revive_common::EVMVersion;
|
||||||
@@ -158,6 +159,26 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_library(
|
||||||
|
mut self,
|
||||||
|
scope: impl AsRef<Path>,
|
||||||
|
library_ident: impl AsRef<str>,
|
||||||
|
library_address: Address,
|
||||||
|
) -> Self {
|
||||||
|
self.input
|
||||||
|
.settings
|
||||||
|
.libraries
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.entry(scope.as_ref().display().to_string())
|
||||||
|
.or_default()
|
||||||
|
.insert(
|
||||||
|
library_ident.as_ref().to_owned(),
|
||||||
|
library_address.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_build(self, solc_path: PathBuf) -> anyhow::Result<CompilerOutput<T::Options>> {
|
pub fn try_build(self, solc_path: PathBuf) -> anyhow::Result<CompilerOutput<T::Options>> {
|
||||||
T::new(solc_path).build(CompilerInput {
|
T::new(solc_path).build(CompilerInput {
|
||||||
extra_options: self.extra_options,
|
extra_options: self.extra_options,
|
||||||
|
|||||||
+266
-131
@@ -26,8 +26,8 @@ use revive_dt_common::iterators::FilesWithExtensionIterator;
|
|||||||
use revive_dt_compiler::{Compiler, SolidityCompiler};
|
use revive_dt_compiler::{Compiler, SolidityCompiler};
|
||||||
use revive_dt_config::Arguments;
|
use revive_dt_config::Arguments;
|
||||||
use revive_dt_format::case::CaseIdx;
|
use revive_dt_format::case::CaseIdx;
|
||||||
use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method};
|
use revive_dt_format::input::{Calldata, EtherValue, Expected, ExpectedOutput, Method};
|
||||||
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier};
|
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdent};
|
||||||
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
|
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
|
||||||
use revive_dt_node::Node;
|
use revive_dt_node::Node;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
@@ -57,6 +57,12 @@ pub struct State<'a, T: Platform> {
|
|||||||
/// files.
|
/// files.
|
||||||
deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>,
|
deployed_contracts: HashMap<CaseIdx, HashMap<ContractInstance, (Address, JsonAbi)>>,
|
||||||
|
|
||||||
|
/// This is a map of the deployed libraries.
|
||||||
|
///
|
||||||
|
/// This map is not per case, but rather, per metadata file. This means that we do not redeploy
|
||||||
|
/// the libraries with each case.
|
||||||
|
deployed_libraries: HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||||
|
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +76,7 @@ where
|
|||||||
span,
|
span,
|
||||||
contracts: Default::default(),
|
contracts: Default::default(),
|
||||||
deployed_contracts: Default::default(),
|
deployed_contracts: Default::default(),
|
||||||
|
deployed_libraries: Default::default(),
|
||||||
phantom: Default::default(),
|
phantom: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,10 +103,28 @@ where
|
|||||||
let compiler = Compiler::<T::Compiler>::new()
|
let compiler = Compiler::<T::Compiler>::new()
|
||||||
.allow_path(metadata.directory()?)
|
.allow_path(metadata.directory()?)
|
||||||
.solc_optimizer(mode.solc_optimize());
|
.solc_optimizer(mode.solc_optimize());
|
||||||
|
let mut compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
||||||
let compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
|
||||||
.with_allowed_extension("sol")
|
.with_allowed_extension("sol")
|
||||||
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
|
.try_fold(compiler, |compiler, path| compiler.with_source(&path))?;
|
||||||
|
for (library_instance, (library_address, _)) in self.deployed_libraries.iter() {
|
||||||
|
let library_ident = &metadata
|
||||||
|
.contracts
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|contracts| contracts.get(library_instance))
|
||||||
|
.expect("Impossible for library to not be found in contracts")
|
||||||
|
.contract_ident;
|
||||||
|
|
||||||
|
// Note the following: we need to tell solc which files require the libraries to be
|
||||||
|
// linked into them. We do not have access to this information and therefore we choose
|
||||||
|
// an easier, yet more compute intensive route, of telling solc that all of the files
|
||||||
|
// need to link the library and it will only perform the linking for the files that do
|
||||||
|
// actually need the library.
|
||||||
|
compiler = FilesWithExtensionIterator::new(metadata.directory()?)
|
||||||
|
.with_allowed_extension("sol")
|
||||||
|
.fold(compiler, |compiler, path| {
|
||||||
|
compiler.with_library(&path, library_ident.as_str(), *library_address)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut task = CompilationTask {
|
let mut task = CompilationTask {
|
||||||
json_input: compiler.input(),
|
json_input: compiler.input(),
|
||||||
@@ -141,6 +166,34 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_and_publish_libraries(
|
||||||
|
&mut self,
|
||||||
|
metadata: &Metadata,
|
||||||
|
mode: &SolcMode,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.build_contracts(mode, metadata)?;
|
||||||
|
|
||||||
|
for library_instance in metadata
|
||||||
|
.libraries
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|(_, map)| map.values())
|
||||||
|
{
|
||||||
|
self.get_or_deploy_contract_instance(
|
||||||
|
library_instance,
|
||||||
|
metadata,
|
||||||
|
None,
|
||||||
|
Input::default_caller(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
node,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_input(
|
pub fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
@@ -173,12 +226,7 @@ where
|
|||||||
|
|
||||||
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
||||||
for instance in input.find_all_contract_instances().into_iter() {
|
for instance in input.find_all_contract_instances().into_iter() {
|
||||||
if !self
|
if !self.deployed_contracts(case_idx).contains_key(&instance) {
|
||||||
.deployed_contracts
|
|
||||||
.entry(case_idx)
|
|
||||||
.or_default()
|
|
||||||
.contains_key(&instance)
|
|
||||||
{
|
|
||||||
instances_we_must_deploy.entry(instance).or_insert(false);
|
instances_we_must_deploy.entry(instance).or_insert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,125 +242,23 @@ where
|
|||||||
|
|
||||||
let mut receipts = HashMap::new();
|
let mut receipts = HashMap::new();
|
||||||
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
for (instance, deploy_with_constructor_arguments) in instances_we_must_deploy.into_iter() {
|
||||||
// What we have at this moment is just a contract instance which is kind of like a variable
|
let calldata = deploy_with_constructor_arguments.then_some(&input.calldata);
|
||||||
// name for an actual underlying contract. So, we need to resolve this instance to the info
|
let value = deploy_with_constructor_arguments
|
||||||
// of the contract that it belongs to.
|
.then_some(input.value)
|
||||||
let Some(ContractPathAndIdentifier {
|
.flatten();
|
||||||
contract_source_path,
|
|
||||||
contract_ident,
|
|
||||||
}) = metadata.contract_sources()?.remove(&instance)
|
|
||||||
else {
|
|
||||||
tracing::error!("Contract source not found for instance");
|
|
||||||
anyhow::bail!("Contract source not found for instance {:?}", instance)
|
|
||||||
};
|
|
||||||
|
|
||||||
let compiled_contract = self.contracts.iter().find_map(|output| {
|
|
||||||
output
|
|
||||||
.contracts
|
|
||||||
.as_ref()?
|
|
||||||
.get(&contract_source_path.display().to_string())
|
|
||||||
.and_then(|source_file_contracts| {
|
|
||||||
source_file_contracts.get(contract_ident.as_ref())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let Some(code) = compiled_contract
|
|
||||||
.and_then(|contract| contract.evm.as_ref().and_then(|evm| evm.bytecode.as_ref()))
|
|
||||||
else {
|
|
||||||
tracing::error!(
|
|
||||||
contract_source_path = contract_source_path.display().to_string(),
|
|
||||||
contract_ident = contract_ident.as_ref(),
|
|
||||||
"Failed to find bytecode for contract"
|
|
||||||
);
|
|
||||||
anyhow::bail!("Failed to find bytecode for contract {:?}", instance)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: When we want to do linking it would be best to do it at this stage here. We have
|
|
||||||
// the context from the metadata files and therefore know what needs to be linked and in
|
|
||||||
// what order it needs to happen.
|
|
||||||
|
|
||||||
let mut code = match alloy::hex::decode(&code.object) {
|
|
||||||
Ok(code) => code,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!(
|
|
||||||
?error,
|
|
||||||
contract_source_path = contract_source_path.display().to_string(),
|
|
||||||
contract_ident = contract_ident.as_ref(),
|
|
||||||
"Failed to hex-decode byte code - This could possibly mean that the bytecode requires linking"
|
|
||||||
);
|
|
||||||
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if deploy_with_constructor_arguments {
|
|
||||||
let encoded_input = input
|
|
||||||
.encoded_input(self.deployed_contracts.entry(case_idx).or_default(), node)?;
|
|
||||||
code.extend(encoded_input.to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
let tx = {
|
|
||||||
let tx = TransactionRequest::default().from(input.caller);
|
|
||||||
let tx = match input.value {
|
|
||||||
Some(ref value) if deploy_with_constructor_arguments => {
|
|
||||||
tx.value(value.into_inner())
|
|
||||||
}
|
|
||||||
_ => tx,
|
|
||||||
};
|
|
||||||
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
|
|
||||||
};
|
|
||||||
|
|
||||||
let receipt = match node.execute_transaction(tx) {
|
|
||||||
Ok(receipt) => receipt,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!(
|
|
||||||
node = std::any::type_name::<T>(),
|
|
||||||
?error,
|
|
||||||
"Contract deployment transaction failed."
|
|
||||||
);
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(address) = receipt.contract_address else {
|
|
||||||
tracing::error!("Contract deployment transaction didn't return an address");
|
|
||||||
anyhow::bail!("Contract deployment didn't return an address");
|
|
||||||
};
|
|
||||||
tracing::info!(
|
|
||||||
instance_name = ?instance,
|
|
||||||
instance_address = ?address,
|
|
||||||
"Deployed contract"
|
|
||||||
);
|
|
||||||
|
|
||||||
let Some(Value::String(metadata)) =
|
|
||||||
compiled_contract.and_then(|contract| contract.metadata.as_ref())
|
|
||||||
else {
|
|
||||||
tracing::error!("Contract does not have a metadata field");
|
|
||||||
anyhow::bail!("Contract does not have a metadata field");
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(metadata) = serde_json::from_str::<Value>(metadata) else {
|
|
||||||
tracing::error!(%metadata, "Failed to parse solc metadata into a structured value");
|
|
||||||
anyhow::bail!("Failed to parse solc metadata into a structured value {metadata}");
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(abi) = metadata.get("output").and_then(|value| value.get("abi")) else {
|
|
||||||
tracing::error!(%metadata, "Failed to access the .output.abi field of the solc metadata");
|
|
||||||
anyhow::bail!(
|
|
||||||
"Failed to access the .output.abi field of the solc metadata {metadata}"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(abi) = serde_json::from_value::<JsonAbi>(abi.clone()) else {
|
|
||||||
tracing::error!(%metadata, "Failed to deserialize ABI into a structured format");
|
|
||||||
anyhow::bail!("Failed to deserialize ABI into a structured format {metadata}");
|
|
||||||
};
|
|
||||||
|
|
||||||
self.deployed_contracts
|
|
||||||
.entry(case_idx)
|
|
||||||
.or_default()
|
|
||||||
.insert(instance.clone(), (address, abi));
|
|
||||||
|
|
||||||
|
if let (_, _, Some(receipt)) = self.get_or_deploy_contract_instance(
|
||||||
|
&instance,
|
||||||
|
metadata,
|
||||||
|
case_idx,
|
||||||
|
input.caller,
|
||||||
|
calldata,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
)? {
|
||||||
receipts.insert(instance.clone(), receipt);
|
receipts.insert(instance.clone(), receipt);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(receipts)
|
Ok(receipts)
|
||||||
}
|
}
|
||||||
@@ -332,9 +278,7 @@ where
|
|||||||
.remove(&input.instance)
|
.remove(&input.instance)
|
||||||
.context("Failed to find deployment receipt"),
|
.context("Failed to find deployment receipt"),
|
||||||
Method::Fallback | Method::FunctionName(_) => {
|
Method::Fallback | Method::FunctionName(_) => {
|
||||||
let tx = match input
|
let tx = match input.legacy_transaction(self.deployed_contracts(case_idx), node) {
|
||||||
.legacy_transaction(self.deployed_contracts.entry(case_idx).or_default(), node)
|
|
||||||
{
|
|
||||||
Ok(tx) => {
|
Ok(tx) => {
|
||||||
tracing::debug!("Legacy transaction data: {tx:#?}");
|
tracing::debug!("Legacy transaction data: {tx:#?}");
|
||||||
tx
|
tx
|
||||||
@@ -442,7 +386,7 @@ where
|
|||||||
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
|
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
|
||||||
// transaction should succeed? Do we just ignore the expectation?
|
// transaction should succeed? Do we just ignore the expectation?
|
||||||
|
|
||||||
let deployed_contracts = self.deployed_contracts.entry(case_idx).or_default();
|
let deployed_contracts = self.deployed_contracts(case_idx);
|
||||||
let chain_state_provider = node;
|
let chain_state_provider = node;
|
||||||
|
|
||||||
// Handling the receipt state assertion.
|
// Handling the receipt state assertion.
|
||||||
@@ -567,6 +511,162 @@ where
|
|||||||
|
|
||||||
Ok((execution_receipt, trace, diff))
|
Ok((execution_receipt, trace, diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deployed_contracts(
|
||||||
|
&mut self,
|
||||||
|
case_idx: impl Into<Option<CaseIdx>>,
|
||||||
|
) -> &mut HashMap<ContractInstance, (Address, JsonAbi)> {
|
||||||
|
match case_idx.into() {
|
||||||
|
Some(case_idx) => self
|
||||||
|
.deployed_contracts
|
||||||
|
.entry(case_idx)
|
||||||
|
.or_insert_with(|| self.deployed_libraries.clone()),
|
||||||
|
None => &mut self.deployed_libraries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the information of a deployed contract or library from the state. If it's found to not
|
||||||
|
/// be deployed then it will be deployed.
|
||||||
|
///
|
||||||
|
/// If a [`CaseIdx`] is not specified then this contact instance address will be stored in the
|
||||||
|
/// cross-case deployed contracts address mapping.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn get_or_deploy_contract_instance(
|
||||||
|
&mut self,
|
||||||
|
contract_instance: &ContractInstance,
|
||||||
|
metadata: &Metadata,
|
||||||
|
case_idx: impl Into<Option<CaseIdx>>,
|
||||||
|
deployer: Address,
|
||||||
|
calldata: Option<&Calldata>,
|
||||||
|
value: Option<EtherValue>,
|
||||||
|
node: &T::Blockchain,
|
||||||
|
) -> anyhow::Result<(Address, JsonAbi, Option<TransactionReceipt>)> {
|
||||||
|
let case_idx = case_idx.into();
|
||||||
|
|
||||||
|
if let Some((address, abi)) = self.deployed_libraries.get(contract_instance) {
|
||||||
|
return Ok((*address, abi.clone(), None));
|
||||||
|
}
|
||||||
|
if let Some(case_idx) = case_idx {
|
||||||
|
if let Some((address, abi)) = self
|
||||||
|
.deployed_contracts
|
||||||
|
.get(&case_idx)
|
||||||
|
.and_then(|contracts| contracts.get(contract_instance))
|
||||||
|
{
|
||||||
|
return Ok((*address, abi.clone(), None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(ContractPathAndIdent {
|
||||||
|
contract_source_path,
|
||||||
|
contract_ident,
|
||||||
|
}) = metadata.contract_sources()?.remove(contract_instance)
|
||||||
|
else {
|
||||||
|
tracing::error!("Contract source not found for instance");
|
||||||
|
anyhow::bail!(
|
||||||
|
"Contract source not found for instance {:?}",
|
||||||
|
contract_instance
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiled_contract = self.contracts.iter().rev().find_map(|output| {
|
||||||
|
output
|
||||||
|
.contracts
|
||||||
|
.as_ref()?
|
||||||
|
.get(&contract_source_path.display().to_string())
|
||||||
|
.and_then(|source_file_contracts| {
|
||||||
|
source_file_contracts.get(contract_ident.as_ref())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let Some(code) = compiled_contract
|
||||||
|
.and_then(|contract| contract.evm.as_ref().and_then(|evm| evm.bytecode.as_ref()))
|
||||||
|
else {
|
||||||
|
tracing::error!(
|
||||||
|
contract_source_path = contract_source_path.display().to_string(),
|
||||||
|
contract_ident = contract_ident.as_ref(),
|
||||||
|
"Failed to find bytecode for contract"
|
||||||
|
);
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to find bytecode for contract {:?}",
|
||||||
|
contract_instance
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut code = match alloy::hex::decode(&code.object) {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
?error,
|
||||||
|
contract_source_path = contract_source_path.display().to_string(),
|
||||||
|
contract_ident = contract_ident.as_ref(),
|
||||||
|
"Failed to hex-decode byte code - This could possibly mean that the bytecode requires linking"
|
||||||
|
);
|
||||||
|
anyhow::bail!("Failed to hex-decode the byte code {}", error)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(Value::String(metadata)) =
|
||||||
|
compiled_contract.and_then(|contract| contract.metadata.as_ref())
|
||||||
|
else {
|
||||||
|
tracing::error!("Contract does not have a metadata field");
|
||||||
|
anyhow::bail!("Contract does not have a metadata field");
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(metadata) = serde_json::from_str::<Value>(metadata) else {
|
||||||
|
tracing::error!(%metadata, "Failed to parse solc metadata into a structured value");
|
||||||
|
anyhow::bail!("Failed to parse solc metadata into a structured value {metadata}");
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(abi) = metadata.get("output").and_then(|value| value.get("abi")) else {
|
||||||
|
tracing::error!(%metadata, "Failed to access the .output.abi field of the solc metadata");
|
||||||
|
anyhow::bail!("Failed to access the .output.abi field of the solc metadata {metadata}");
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(abi) = serde_json::from_value::<JsonAbi>(abi.clone()) else {
|
||||||
|
tracing::error!(%metadata, "Failed to deserialize ABI into a structured format");
|
||||||
|
anyhow::bail!("Failed to deserialize ABI into a structured format {metadata}");
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(calldata) = calldata {
|
||||||
|
let calldata = calldata.calldata(self.deployed_contracts(case_idx), node)?;
|
||||||
|
code.extend(calldata);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = {
|
||||||
|
let tx = TransactionRequest::default().from(deployer);
|
||||||
|
let tx = match value {
|
||||||
|
Some(ref value) => tx.value(value.into_inner()),
|
||||||
|
_ => tx,
|
||||||
|
};
|
||||||
|
TransactionBuilder::<Ethereum>::with_deploy_code(tx, code)
|
||||||
|
};
|
||||||
|
|
||||||
|
let receipt = match node.execute_transaction(tx) {
|
||||||
|
Ok(receipt) => receipt,
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(
|
||||||
|
node = std::any::type_name::<T>(),
|
||||||
|
?error,
|
||||||
|
"Contract deployment transaction failed."
|
||||||
|
);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(address) = receipt.contract_address else {
|
||||||
|
tracing::error!("Contract deployment transaction didn't return an address");
|
||||||
|
anyhow::bail!("Contract deployment didn't return an address");
|
||||||
|
};
|
||||||
|
tracing::info!(
|
||||||
|
instance_name = ?contract_instance,
|
||||||
|
instance_address = ?address,
|
||||||
|
"Deployed contract"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.deployed_contracts(case_idx)
|
||||||
|
.insert(contract_instance.clone(), (address, abi.clone()));
|
||||||
|
|
||||||
|
Ok((address, abi, Some(receipt)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Driver<'a, Leader: Platform, Follower: Platform> {
|
pub struct Driver<'a, Leader: Platform, Follower: Platform> {
|
||||||
@@ -673,6 +773,42 @@ where
|
|||||||
let mut leader_state = State::<L>::new(self.config, span);
|
let mut leader_state = State::<L>::new(self.config, span);
|
||||||
let mut follower_state = State::<F>::new(self.config, span);
|
let mut follower_state = State::<F>::new(self.config, span);
|
||||||
|
|
||||||
|
// Note: we are currently forced to do two compilation passes due to linking. In the
|
||||||
|
// first compilation pass we compile the libraries and publish them to the chain. In the
|
||||||
|
// second compilation pass we compile the contracts with the library addresses so that
|
||||||
|
// they're linked at compile-time.
|
||||||
|
let build_result = tracing::info_span!("Building and publishing libraries")
|
||||||
|
.in_scope(|| {
|
||||||
|
match leader_state.build_and_publish_libraries(self.metadata, &mode, self.leader_node) {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::debug!(target = ?Target::Leader, "Library building succeeded");
|
||||||
|
execution_result.add_successful_build(Target::Leader, mode.clone());
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(target = ?Target::Leader, ?error, "Library building failed");
|
||||||
|
execution_result.add_failed_build(Target::Leader, mode.clone(), error);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match follower_state.build_and_publish_libraries(self.metadata, &mode, self.follower_node) {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::debug!(target = ?Target::Follower, "Library building succeeded");
|
||||||
|
execution_result.add_successful_build(Target::Follower, mode.clone());
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(target = ?Target::Follower, ?error, "Library building failed");
|
||||||
|
execution_result.add_failed_build(Target::Follower, mode.clone(), error);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
if build_result.is_err() {
|
||||||
|
// Note: We skip to the next solc mode as there's nothing that we can do at this
|
||||||
|
// point, the building has failed. We do NOT bail out of the execution as a whole.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// We build the contracts. If building the contracts for the metadata file fails then we
|
// We build the contracts. If building the contracts for the metadata file fails then we
|
||||||
// have no other option but to keep note of this error and move on to the next solc mode
|
// have no other option but to keep note of this error and move on to the next solc mode
|
||||||
// and NOT just bail out of the execution as a whole.
|
// and NOT just bail out of the execution as a whole.
|
||||||
@@ -709,7 +845,6 @@ where
|
|||||||
|
|
||||||
// For cases if one of the inputs fail then we move on to the next case and we do NOT
|
// For cases if one of the inputs fail then we move on to the next case and we do NOT
|
||||||
// bail out of the whole thing.
|
// bail out of the whole thing.
|
||||||
|
|
||||||
'case_loop: for (case_idx, case) in self.metadata.cases.iter().enumerate() {
|
'case_loop: for (case_idx, case) in self.metadata.cases.iter().enumerate() {
|
||||||
let tracing_span = tracing::info_span!(
|
let tracing_span = tracing::info_span!(
|
||||||
"Handling case",
|
"Handling case",
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ impl Deref for MetadataFile {
|
|||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub targets: Option<Vec<String>>,
|
pub targets: Option<Vec<String>>,
|
||||||
pub cases: Vec<Case>,
|
pub cases: Vec<Case>,
|
||||||
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdentifier>>,
|
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>,
|
||||||
// TODO: Convert into wrapper types for clarity.
|
// TODO: Convert into wrapper types for clarity.
|
||||||
pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>,
|
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
|
||||||
pub ignore: Option<bool>,
|
pub ignore: Option<bool>,
|
||||||
pub modes: Option<Vec<Mode>>,
|
pub modes: Option<Vec<Mode>>,
|
||||||
pub file_path: Option<PathBuf>,
|
pub file_path: Option<PathBuf>,
|
||||||
@@ -86,7 +86,7 @@ impl Metadata {
|
|||||||
/// Returns the contract sources with canonicalized paths for the files
|
/// Returns the contract sources with canonicalized paths for the files
|
||||||
pub fn contract_sources(
|
pub fn contract_sources(
|
||||||
&self,
|
&self,
|
||||||
) -> anyhow::Result<BTreeMap<ContractInstance, ContractPathAndIdentifier>> {
|
) -> anyhow::Result<BTreeMap<ContractInstance, ContractPathAndIdent>> {
|
||||||
let directory = self.directory()?;
|
let directory = self.directory()?;
|
||||||
let mut sources = BTreeMap::new();
|
let mut sources = BTreeMap::new();
|
||||||
let Some(contracts) = &self.contracts else {
|
let Some(contracts) = &self.contracts else {
|
||||||
@@ -95,7 +95,7 @@ impl Metadata {
|
|||||||
|
|
||||||
for (
|
for (
|
||||||
alias,
|
alias,
|
||||||
ContractPathAndIdentifier {
|
ContractPathAndIdent {
|
||||||
contract_source_path,
|
contract_source_path,
|
||||||
contract_ident,
|
contract_ident,
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@ impl Metadata {
|
|||||||
|
|
||||||
sources.insert(
|
sources.insert(
|
||||||
alias,
|
alias,
|
||||||
ContractPathAndIdentifier {
|
ContractPathAndIdent {
|
||||||
contract_source_path: absolute_path,
|
contract_source_path: absolute_path,
|
||||||
contract_ident,
|
contract_ident,
|
||||||
},
|
},
|
||||||
@@ -194,7 +194,7 @@ impl Metadata {
|
|||||||
metadata.contracts = Some(
|
metadata.contracts = Some(
|
||||||
[(
|
[(
|
||||||
ContractInstance::new("test"),
|
ContractInstance::new("test"),
|
||||||
ContractPathAndIdentifier {
|
ContractPathAndIdent {
|
||||||
contract_source_path: path.to_path_buf(),
|
contract_source_path: path.to_path_buf(),
|
||||||
contract_ident: ContractIdent::new("Test"),
|
contract_ident: ContractIdent::new("Test"),
|
||||||
},
|
},
|
||||||
@@ -245,7 +245,7 @@ define_wrapper_type!(
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
#[serde(try_from = "String", into = "String")]
|
#[serde(try_from = "String", into = "String")]
|
||||||
pub struct ContractPathAndIdentifier {
|
pub struct ContractPathAndIdent {
|
||||||
/// The path of the contract source code relative to the directory containing the metadata file.
|
/// The path of the contract source code relative to the directory containing the metadata file.
|
||||||
pub contract_source_path: PathBuf,
|
pub contract_source_path: PathBuf,
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ pub struct ContractPathAndIdentifier {
|
|||||||
pub contract_ident: ContractIdent,
|
pub contract_ident: ContractIdent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ContractPathAndIdentifier {
|
impl Display for ContractPathAndIdent {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@@ -264,7 +264,7 @@ impl Display for ContractPathAndIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ContractPathAndIdentifier {
|
impl FromStr for ContractPathAndIdent {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
@@ -300,7 +300,7 @@ impl FromStr for ContractPathAndIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for ContractPathAndIdentifier {
|
impl TryFrom<String> for ContractPathAndIdent {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
@@ -308,8 +308,8 @@ impl TryFrom<String> for ContractPathAndIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ContractPathAndIdentifier> for String {
|
impl From<ContractPathAndIdent> for String {
|
||||||
fn from(value: ContractPathAndIdentifier) -> Self {
|
fn from(value: ContractPathAndIdent) -> Self {
|
||||||
value.to_string()
|
value.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,7 @@ mod test {
|
|||||||
let string = "ERC20/ERC20.sol:ERC20";
|
let string = "ERC20/ERC20.sol:ERC20";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let identifier = ContractPathAndIdentifier::from_str(string);
|
let identifier = ContractPathAndIdent::from_str(string);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let identifier = identifier.expect("Failed to parse");
|
let identifier = identifier.expect("Failed to parse");
|
||||||
|
|||||||
Reference in New Issue
Block a user