mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-29 04:17:59 +00:00
Merge branch 'main' into jsdw-redo-modes
This commit is contained in:
+23
-11
@@ -1,32 +1,44 @@
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_dt_common::macros::define_wrapper_type;
|
||||
|
||||
use crate::{
|
||||
input::{Expected, Input},
|
||||
input::{Expected, Step},
|
||||
mode::ParsedMode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct Case {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub modes: Option<Vec<ParsedMode>>,
|
||||
pub inputs: Vec<Input>,
|
||||
#[serde(rename = "inputs")]
|
||||
pub steps: Vec<Step>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub group: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expected: Option<Expected>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ignore: Option<bool>,
|
||||
}
|
||||
|
||||
impl Case {
|
||||
pub fn inputs_iterator(&self) -> impl Iterator<Item = Input> {
|
||||
let inputs_len = self.inputs.len();
|
||||
self.inputs
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
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 input)| {
|
||||
if idx + 1 == inputs_len {
|
||||
.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();
|
||||
}
|
||||
@@ -36,9 +48,9 @@ impl Case {
|
||||
// the case? What are we supposed to do with that final expected field on the
|
||||
// case?
|
||||
|
||||
input
|
||||
step
|
||||
} else {
|
||||
input
|
||||
step
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,23 +17,79 @@ use revive_dt_common::macros::define_wrapper_type;
|
||||
use crate::traits::ResolverApi;
|
||||
use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
/// A test step.
|
||||
///
|
||||
/// A test step can be anything. It could be an invocation to a function, an assertion, or any other
|
||||
/// action that needs to be run or executed on the nodes used in the tests.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Step {
|
||||
/// A function call or an invocation to some function on some smart contract.
|
||||
FunctionCall(Box<Input>),
|
||||
/// A step for performing a balance assertion on some account or contract.
|
||||
BalanceAssertion(Box<BalanceAssertion>),
|
||||
/// A step for asserting that the storage of some contract or account is empty.
|
||||
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Input {
|
||||
#[serde(default = "Input::default_caller")]
|
||||
pub caller: Address,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
#[serde(default = "Input::default_instance")]
|
||||
pub instance: ContractInstance,
|
||||
pub method: Method,
|
||||
#[serde(default)]
|
||||
pub calldata: Calldata,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expected: Option<Expected>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<EtherValue>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage: Option<HashMap<String, Calldata>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub variable_assignments: Option<VariableAssignments>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct BalanceAssertion {
|
||||
/// An optional comment on the balance assertion.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
|
||||
/// The address that the balance assertion should be done on.
|
||||
///
|
||||
/// This is a string which will be resolved into an address when being processed. Therefore,
|
||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
||||
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||
/// followed in the calldata.
|
||||
pub address: String,
|
||||
|
||||
/// The amount of balance to assert that the account or contract has.
|
||||
pub expected_balance: U256,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct StorageEmptyAssertion {
|
||||
/// An optional comment on the storage empty assertion.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
|
||||
/// The address that the balance assertion should be done on.
|
||||
///
|
||||
/// This is a string which will be resolved into an address when being processed. Therefore,
|
||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
||||
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||
/// followed in the calldata.
|
||||
pub address: String,
|
||||
|
||||
/// A boolean of whether the storage of the address is empty or not.
|
||||
pub is_storage_empty: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Expected {
|
||||
Calldata(Calldata),
|
||||
@@ -41,17 +97,21 @@ pub enum Expected {
|
||||
ExpectedMany(Vec<ExpectedOutput>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct ExpectedOutput {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub compiler_version: Option<VersionReq>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub return_data: Option<Calldata>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub events: Option<Vec<Event>>,
|
||||
#[serde(default)]
|
||||
pub exception: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Event {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub address: Option<String>,
|
||||
pub topics: Vec<String>,
|
||||
pub values: Calldata,
|
||||
@@ -108,7 +168,7 @@ pub struct Event {
|
||||
/// [`Single`]: Calldata::Single
|
||||
/// [`Compound`]: Calldata::Compound
|
||||
/// [reverse polish notation]: https://en.wikipedia.org/wiki/Reverse_Polish_notation
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Calldata {
|
||||
Single(Bytes),
|
||||
@@ -142,7 +202,7 @@ enum Operation {
|
||||
}
|
||||
|
||||
/// Specify how the contract is called.
|
||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub enum Method {
|
||||
/// Initiate a deploy transaction, calling contracts constructor.
|
||||
///
|
||||
@@ -167,7 +227,7 @@ define_wrapper_type!(
|
||||
pub struct EtherValue(U256);
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct VariableAssignments {
|
||||
/// A vector of the variable names to assign to the return data.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::BTreeMap,
|
||||
fmt::Display,
|
||||
fs::{File, read_to_string},
|
||||
@@ -9,6 +10,7 @@ use std::{
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revive_common::EVMVersion;
|
||||
use revive_dt_common::{iterators::FilesWithExtensionIterator, macros::define_wrapper_type};
|
||||
|
||||
use crate::{
|
||||
@@ -43,16 +45,26 @@ impl Deref for MetadataFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct Metadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub targets: Option<Vec<String>>,
|
||||
pub cases: Vec<Case>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contracts: Option<BTreeMap<ContractInstance, ContractPathAndIdent>>,
|
||||
// TODO: Convert into wrapper types for clarity.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub libraries: Option<BTreeMap<PathBuf, BTreeMap<ContractIdent, ContractInstance>>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ignore: Option<bool>,
|
||||
pub modes: Option<Vec<ParsedMode>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file_path: Option<PathBuf>,
|
||||
|
||||
/// This field specifies an EVM version requirement that the test case has
|
||||
/// where the test might be run of the evm version of the nodes match the
|
||||
/// evm version specified here.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub required_evm_version: Option<EvmVersionRequirement>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
@@ -334,6 +346,131 @@ impl From<ContractPathAndIdent> for String {
|
||||
}
|
||||
}
|
||||
|
||||
/// An EVM version requirement that the test case has. This gets serialized and
|
||||
/// deserialized from and into [`String`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct EvmVersionRequirement {
|
||||
ordering: Ordering,
|
||||
or_equal: bool,
|
||||
evm_version: EVMVersion,
|
||||
}
|
||||
|
||||
impl EvmVersionRequirement {
|
||||
pub fn new_greater_than_or_equals(version: EVMVersion) -> Self {
|
||||
Self {
|
||||
ordering: Ordering::Greater,
|
||||
or_equal: true,
|
||||
evm_version: version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_greater_than(version: EVMVersion) -> Self {
|
||||
Self {
|
||||
ordering: Ordering::Greater,
|
||||
or_equal: false,
|
||||
evm_version: version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_equals(version: EVMVersion) -> Self {
|
||||
Self {
|
||||
ordering: Ordering::Equal,
|
||||
or_equal: false,
|
||||
evm_version: version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_less_than(version: EVMVersion) -> Self {
|
||||
Self {
|
||||
ordering: Ordering::Less,
|
||||
or_equal: false,
|
||||
evm_version: version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_less_than_or_equals(version: EVMVersion) -> Self {
|
||||
Self {
|
||||
ordering: Ordering::Less,
|
||||
or_equal: true,
|
||||
evm_version: version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, other: &EVMVersion) -> bool {
|
||||
let ordering = other.cmp(&self.evm_version);
|
||||
ordering == self.ordering || (self.or_equal && matches!(ordering, Ordering::Equal))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EvmVersionRequirement {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self {
|
||||
ordering,
|
||||
or_equal,
|
||||
evm_version,
|
||||
} = self;
|
||||
match ordering {
|
||||
Ordering::Less => write!(f, "<")?,
|
||||
Ordering::Equal => write!(f, "=")?,
|
||||
Ordering::Greater => write!(f, ">")?,
|
||||
}
|
||||
if *or_equal && !matches!(ordering, Ordering::Equal) {
|
||||
write!(f, "=")?;
|
||||
}
|
||||
write!(f, "{evm_version}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EvmVersionRequirement {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.as_bytes() {
|
||||
[b'>', b'=', remaining @ ..] => Ok(Self {
|
||||
ordering: Ordering::Greater,
|
||||
or_equal: true,
|
||||
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||
}),
|
||||
[b'>', remaining @ ..] => Ok(Self {
|
||||
ordering: Ordering::Greater,
|
||||
or_equal: false,
|
||||
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||
}),
|
||||
[b'<', b'=', remaining @ ..] => Ok(Self {
|
||||
ordering: Ordering::Less,
|
||||
or_equal: true,
|
||||
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||
}),
|
||||
[b'<', remaining @ ..] => Ok(Self {
|
||||
ordering: Ordering::Less,
|
||||
or_equal: false,
|
||||
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||
}),
|
||||
[b'=', remaining @ ..] => Ok(Self {
|
||||
ordering: Ordering::Equal,
|
||||
or_equal: false,
|
||||
evm_version: str::from_utf8(remaining)?.try_into()?,
|
||||
}),
|
||||
_ => anyhow::bail!("Invalid EVM version requirement {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for EvmVersionRequirement {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
value.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EvmVersionRequirement> for String {
|
||||
fn from(value: EvmVersionRequirement) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user