This commit is contained in:
pgherveou
2025-10-08 08:22:26 +02:00
parent 6e64f678ee
commit 765569a8b6
39 changed files with 662 additions and 1178 deletions
+19 -25
View File
@@ -54,39 +54,33 @@ pub struct Case {
impl Case {
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
let steps_len = self.steps.len();
self.steps
.clone()
.into_iter()
.enumerate()
.map(move |(idx, mut step)| {
let Step::FunctionCall(ref mut input) = step else {
return step;
};
self.steps.clone().into_iter().enumerate().map(move |(idx, mut step)| {
let Step::FunctionCall(ref mut input) = step else {
return step;
};
if idx + 1 == steps_len {
if input.expected.is_none() {
input.expected = self.expected.clone();
}
// TODO: What does it mean for us to have an `expected` field on the case itself
// but the final input also has an expected field that doesn't match the one on
// the case? What are we supposed to do with that final expected field on the
// case?
step
} else {
step
if idx + 1 == steps_len {
if input.expected.is_none() {
input.expected = self.expected.clone();
}
})
// TODO: What does it mean for us to have an `expected` field on the case itself
// but the final input also has an expected field that doesn't match the one on
// the case? What are we supposed to do with that final expected field on the
// case?
step
} else {
step
}
})
}
pub fn steps_iterator_for_benchmarks(
&self,
default_repeat_count: usize,
) -> Box<dyn Iterator<Item = Step> + '_> {
let contains_repeat = self
.steps_iterator()
.any(|step| matches!(&step, Step::Repeat(..)));
let contains_repeat = self.steps_iterator().any(|step| matches!(&step, Step::Repeat(..)));
if contains_repeat {
Box::new(self.steps_iterator()) as Box<_>
} else {
+40 -22
View File
@@ -13,8 +13,14 @@ use anyhow::Context as _;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Corpus {
SinglePath { name: String, path: PathBuf },
MultiplePaths { name: String, paths: Vec<PathBuf> },
SinglePath {
name: String,
path: PathBuf,
},
MultiplePaths {
name: String,
paths: Vec<PathBuf>,
},
}
impl Corpus {
@@ -86,46 +92,58 @@ impl Corpus {
.collect::<Vec<_>>();
tests.sort_by(|a, b| a.metadata_file_path.cmp(&b.metadata_file_path));
tests.dedup_by(|a, b| a.metadata_file_path == b.metadata_file_path);
info!(
len = tests.len(),
corpus_name = self.name(),
"Found tests in Corpus"
);
info!(len = tests.len(), corpus_name = self.name(), "Found tests in Corpus");
tests
}
pub fn name(&self) -> &str {
match self {
Corpus::SinglePath { name, .. } | Corpus::MultiplePaths { name, .. } => name.as_str(),
Corpus::SinglePath {
name,
..
}
| Corpus::MultiplePaths {
name,
..
} => name.as_str(),
}
}
pub fn paths_iter(&self) -> impl Iterator<Item = &Path> {
match self {
Corpus::SinglePath { path, .. } => {
Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>
}
Corpus::MultiplePaths { paths, .. } => {
Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>
}
Corpus::SinglePath {
path,
..
} => Box::new(std::iter::once(path.as_path())) as Box<dyn Iterator<Item = _>>,
Corpus::MultiplePaths {
paths,
..
} => Box::new(paths.iter().map(|path| path.as_path())) as Box<dyn Iterator<Item = _>>,
}
}
pub fn paths_iter_mut(&mut self) -> impl Iterator<Item = &mut PathBuf> {
match self {
Corpus::SinglePath { path, .. } => {
Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>
}
Corpus::MultiplePaths { paths, .. } => {
Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>
}
Corpus::SinglePath {
path,
..
} => Box::new(std::iter::once(path)) as Box<dyn Iterator<Item = _>>,
Corpus::MultiplePaths {
paths,
..
} => Box::new(paths.iter_mut()) as Box<dyn Iterator<Item = _>>,
}
}
pub fn path_count(&self) -> usize {
match self {
Corpus::SinglePath { .. } => 1,
Corpus::MultiplePaths { paths, .. } => paths.len(),
Corpus::SinglePath {
..
} => 1,
Corpus::MultiplePaths {
paths,
..
} => paths.len(),
}
}
}
+5 -17
View File
@@ -44,9 +44,7 @@ impl MetadataFile {
if self.corpus_file_path.is_file() {
&self.corpus_file_path
} else {
self.metadata_file_path
.strip_prefix(&self.corpus_file_path)
.unwrap()
self.metadata_file_path.strip_prefix(&self.corpus_file_path).unwrap()
}
}
}
@@ -167,10 +165,8 @@ impl Metadata {
) in contracts
{
let alias = alias.clone();
let absolute_path = directory
.join(contract_source_path)
.canonicalize()
.map_err(|error| {
let absolute_path =
directory.join(contract_source_path).canonicalize().map_err(|error| {
anyhow::anyhow!(
"Failed to canonicalize contract source path '{}': {error}",
directory.join(contract_source_path).display()
@@ -335,12 +331,7 @@ pub struct ContractPathAndIdent {
impl Display for ContractPathAndIdent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}",
self.contract_source_path.display(),
self.contract_ident.as_ref()
)
write!(f, "{}:{}", self.contract_source_path.display(), self.contract_ident.as_ref())
}
}
@@ -596,10 +587,7 @@ mod test {
// Assert
let identifier = identifier.expect("Failed to parse");
assert_eq!(
identifier.contract_source_path.display().to_string(),
"ERC20/ERC20.sol"
);
assert_eq!(identifier.contract_source_path.display().to_string(), "ERC20/ERC20.sol");
assert_eq!(identifier.contract_ident, "ERC20".to_owned().into());
// Act
+16 -18
View File
@@ -1,13 +1,12 @@
use anyhow::Context as _;
use regex::Regex;
use revive_dt_common::iterators::EitherIter;
use revive_dt_common::types::{Mode, ModeOptimizerSetting, ModePipeline};
use revive_dt_common::{
iterators::EitherIter,
types::{Mode, ModeOptimizerSetting, ModePipeline},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{collections::HashSet, fmt::Display, str::FromStr, sync::LazyLock};
/// This represents a mode that has been parsed from test metadata.
///
@@ -94,7 +93,11 @@ impl Display for ParsedMode {
if let Some(pipeline) = self.pipeline {
pipeline.fmt(f)?;
if let Some(optimize_flag) = self.optimize_flag {
f.write_str(if optimize_flag { "+" } else { "-" })?;
f.write_str(if optimize_flag {
"+"
} else {
"-"
})?;
}
has_written = true;
}
@@ -158,13 +161,11 @@ impl ParsedMode {
);
pipeline_iter.flat_map(move |pipeline| {
optimize_settings_iter
.clone()
.map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
optimize_settings_iter.clone().map(move |optimize_setting| Mode {
pipeline,
optimize_setting,
version: self.version.clone(),
})
})
}
@@ -236,10 +237,7 @@ mod tests {
("Y+", vec!["Y M3"]),
("Y-", vec!["Y M0"]),
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
(
"<=0.8",
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
),
("<=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"]),
];
for (actual, expected) in strings {
+34 -109
View File
@@ -1,11 +1,10 @@
use std::{collections::HashMap, fmt::Display, str::FromStr};
use alloy::primitives::{FixedBytes, utils::parse_units};
use alloy::{
eips::BlockNumberOrTag,
json_abi::Function,
network::TransactionBuilder,
primitives::{Address, Bytes, U256},
primitives::{Address, Bytes, FixedBytes, U256, utils::parse_units},
rpc::types::TransactionRequest,
};
use anyhow::Context as _;
@@ -17,8 +16,10 @@ use serde::{Deserialize, Serialize};
use revive_dt_common::macros::define_wrapper_type;
use tracing::{Instrument, info_span, instrument};
use crate::traits::ResolverApi;
use crate::{metadata::ContractInstance, traits::ResolutionContext};
use crate::{
metadata::ContractInstance,
traits::{ResolutionContext, ResolverApi},
};
/// A test step.
///
@@ -77,12 +78,7 @@ impl StepPath {
impl Display for StepPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0
.iter()
.map(|idx| idx.to_string())
.collect::<Vec<_>>()
.join(".")
.fmt(f)
self.0.iter().map(|idx| idx.to_string()).collect::<Vec<_>>().join(".").fmt(f)
}
}
@@ -90,10 +86,7 @@ impl FromStr for StepPath {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(".")
.map(StepIdx::from_str)
.collect::<anyhow::Result<Vec<_>>>()
.map(Self)
s.split(".").map(StepIdx::from_str).collect::<anyhow::Result<Vec<_>>>().map(Self)
}
}
@@ -455,9 +448,7 @@ impl StepAddress {
impl FunctionCallStep {
pub const fn default_caller_address() -> Address {
Address(FixedBytes(alloy::hex!(
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
)))
Address(FixedBytes(alloy::hex!("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")))
}
pub const fn default_caller() -> StepAddress {
@@ -547,11 +538,9 @@ impl FunctionCallStep {
.await
.context("Failed to encode input bytes for transaction request")?;
let caller = self.caller.resolve_address(resolver, context).await?;
let transaction_request = TransactionRequest::default().from(caller).value(
self.value
.map(|value| value.into_inner())
.unwrap_or_default(),
);
let transaction_request = TransactionRequest::default()
.from(caller)
.value(self.value.map(|value| value.into_inner()).unwrap_or_default());
match self.method {
Method::Deployer => Ok(transaction_request.with_deploy_code(input_data)),
_ => Ok(transaction_request
@@ -607,11 +596,7 @@ impl Calldata {
pub fn new_compound(items: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
Self::Compound(
items
.into_iter()
.map(|item| item.as_ref().to_owned())
.map(CalldataItem::new)
.collect(),
items.into_iter().map(|item| item.as_ref().to_owned()).map(CalldataItem::new).collect(),
)
}
@@ -633,8 +618,7 @@ impl Calldata {
context: ResolutionContext<'_>,
) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
self.calldata_into_slice(&mut buffer, resolver, context)
.await?;
self.calldata_into_slice(&mut buffer, resolver, context).await?;
Ok(buffer)
}
@@ -725,10 +709,7 @@ impl CalldataItem {
) -> anyhow::Result<U256> {
let mut stack = Vec::<CalldataToken<U256>>::new();
for token in self
.calldata_tokens()
.map(|token| token.resolve(resolver, context))
{
for token in self.calldata_tokens().map(|token| token.resolve(resolver, context)) {
let token = token.await?;
let new_token = match token {
CalldataToken::Item(_) => token,
@@ -769,9 +750,7 @@ impl CalldataItem {
// Empty stack means that we got an empty compound calldata which we resolve to zero.
[] => Ok(U256::ZERO),
[CalldataToken::Item(item)] => Ok(*item),
_ => Err(anyhow::anyhow!(
"Invalid calldata arithmetic operation - Invalid stack"
)),
_ => Err(anyhow::anyhow!("Invalid calldata arithmetic operation - Invalid stack")),
}
}
@@ -915,10 +894,7 @@ impl<T: AsRef<str>> CalldataToken<T> {
.await
.map(U256::from)
} else if let Some(variable_name) = item.strip_prefix(Self::VARIABLE_PREFIX) {
context
.variable(variable_name)
.context("Variable lookup failed")
.copied()
context.variable(variable_name).context("Variable lookup failed").copied()
} else {
U256::from_str_radix(item, 10)
.map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))
@@ -959,9 +935,12 @@ impl<'de> Deserialize<'de> for EtherValue {
#[cfg(test)]
mod tests {
use alloy::primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address};
use alloy::sol_types::SolValue;
use alloy::{eips::BlockNumberOrTag, json_abi::JsonAbi};
use alloy::{
eips::BlockNumberOrTag,
json_abi::JsonAbi,
primitives::{BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, address},
sol_types::SolValue,
};
use std::{collections::HashMap, pin::Pin};
use super::*;
@@ -1045,13 +1024,7 @@ mod tests {
"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_metadata).unwrap();
let selector = parsed_abi
.function("store")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("store").unwrap().first().unwrap().selector().0;
let input = FunctionCallStep {
instance: ContractInstance::new("Contract"),
@@ -1089,13 +1062,7 @@ mod tests {
]"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap();
let selector = parsed_abi
.function("send")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("send").unwrap().first().unwrap().selector().0;
let input: FunctionCallStep = FunctionCallStep {
instance: "Contract".to_owned().into(),
@@ -1117,10 +1084,7 @@ mod tests {
type T = (alloy::primitives::Address,);
let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap();
assert_eq!(
decoded.0,
address!("0x1000000000000000000000000000000000000001")
);
assert_eq!(decoded.0, address!("0x1000000000000000000000000000000000000001"));
}
#[tokio::test]
@@ -1136,13 +1100,7 @@ mod tests {
]"#;
let parsed_abi: JsonAbi = serde_json::from_str(raw_abi).unwrap();
let selector = parsed_abi
.function("send")
.unwrap()
.first()
.unwrap()
.selector()
.0;
let selector = parsed_abi.function("send").unwrap().first().unwrap().selector().0;
let input: FunctionCallStep = FunctionCallStep {
instance: ContractInstance::new("Contract"),
@@ -1164,10 +1122,7 @@ mod tests {
type T = (alloy::primitives::Address,);
let decoded: T = T::abi_decode(&encoded.0[4..]).unwrap();
assert_eq!(
decoded.0,
address!("0x1000000000000000000000000000000000000001")
);
assert_eq!(decoded.0, address!("0x1000000000000000000000000000000000000001"));
}
async fn resolve_calldata_item(
@@ -1204,12 +1159,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
MockResolver
.block_gas_limit(Default::default())
.await
.unwrap()
)
U256::from(MockResolver.block_gas_limit(Default::default()).await.unwrap())
)
}
@@ -1226,11 +1176,7 @@ mod tests {
assert_eq!(
resolved,
U256::from_be_slice(
MockResolver
.block_coinbase(Default::default())
.await
.unwrap()
.as_ref()
MockResolver.block_coinbase(Default::default()).await.unwrap().as_ref()
)
)
}
@@ -1245,13 +1191,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
MockResolver
.block_difficulty(Default::default())
.await
.unwrap()
)
assert_eq!(resolved, MockResolver.block_difficulty(Default::default()).await.unwrap())
}
#[tokio::test]
@@ -1266,11 +1206,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
MockResolver
.block_base_fee(Default::default())
.await
.map(U256::from)
.unwrap()
MockResolver.block_base_fee(Default::default()).await.map(U256::from).unwrap()
)
}
@@ -1300,10 +1236,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(MockResolver.last_block_number().await.unwrap())
)
assert_eq!(resolved, U256::from(MockResolver.last_block_number().await.unwrap()))
}
#[tokio::test]
@@ -1318,12 +1251,7 @@ mod tests {
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(
MockResolver
.block_timestamp(Default::default())
.await
.unwrap()
)
U256::from(MockResolver.block_timestamp(Default::default()).await.unwrap())
)
}
@@ -1401,10 +1329,7 @@ mod tests {
// Assert
let resolved = resolved.expect("Failed to resolve argument");
assert_eq!(
resolved,
U256::from(MockResolver.last_block_number().await.unwrap() + 10)
);
assert_eq!(resolved, U256::from(MockResolver.last_block_number().await.unwrap() + 10));
}
#[tokio::test]
+8 -10
View File
@@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::pin::Pin;
use std::{collections::HashMap, pin::Pin};
use alloy::eips::BlockNumberOrTag;
use alloy::json_abi::JsonAbi;
use alloy::primitives::TxHash;
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
use alloy::{
eips::BlockNumberOrTag,
json_abi::JsonAbi,
primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, TxHash, U256},
};
use anyhow::Result;
use crate::metadata::{ContractIdent, ContractInstance};
@@ -149,8 +149,7 @@ impl<'a> ResolutionContext<'a> {
&self,
instance: &ContractInstance,
) -> Option<&(ContractIdent, Address, JsonAbi)> {
self.deployed_contracts
.and_then(|deployed_contracts| deployed_contracts.get(instance))
self.deployed_contracts.and_then(|deployed_contracts| deployed_contracts.get(instance))
}
pub fn deployed_contract_address(&self, instance: &ContractInstance) -> Option<&Address> {
@@ -162,8 +161,7 @@ impl<'a> ResolutionContext<'a> {
}
pub fn variable(&self, name: impl AsRef<str>) -> Option<&U256> {
self.variables
.and_then(|variables| variables.get(name.as_ref()))
self.variables.and_then(|variables| variables.get(name.as_ref()))
}
pub fn tip_block_number(&self) -> Option<&'a BlockNumber> {