Add step paths (#163)

* Support repetitions in the tool

* Add support for account allocations

* Update the JSON schema

* Support virtual repeats

* Add a step path

* Update the schema
This commit is contained in:
Omar
2025-09-22 06:53:16 +03:00
committed by GitHub
parent 97e3f8bbff
commit 3dda739cef
4 changed files with 79 additions and 14 deletions
+12 -9
View File
@@ -30,7 +30,7 @@ use revive_dt_format::case::Case;
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
use revive_dt_format::steps::{
BalanceAssertionStep, Calldata, EtherValue, Expected, ExpectedOutput, FunctionCallStep, Method,
StepIdx, StorageEmptyAssertionStep,
StepIdx, StepPath, StorageEmptyAssertionStep,
};
use revive_dt_format::{metadata::Metadata, steps::Step};
use revive_dt_node_interaction::EthereumNode;
@@ -83,6 +83,7 @@ impl CaseState {
&mut self,
metadata: &Metadata,
step: &Step,
step_path: &StepPath,
node: &dyn EthereumNode,
) -> anyhow::Result<StepOutput> {
match step {
@@ -110,6 +111,7 @@ impl CaseState {
metadata,
repetition_step.repeat,
&repetition_step.steps,
step_path,
node,
)
.await
@@ -200,13 +202,15 @@ impl CaseState {
metadata: &Metadata,
repetitions: usize,
steps: &[Step],
step_path: &StepPath,
node: &dyn EthereumNode,
) -> anyhow::Result<()> {
let tasks = (0..repetitions).map(|_| {
let mut state = self.clone();
async move {
for step in steps {
state.handle_step(metadata, step, node).await?;
for (step_idx, step) in steps.iter().enumerate() {
let step_path = step_path.append(step_idx);
state.handle_step(metadata, step, &step_path, node).await?;
}
Ok::<(), anyhow::Error>(())
}
@@ -842,32 +846,31 @@ impl<'a> CaseDriver<'a> {
.enumerate()
.map(|(idx, v)| (StepIdx::new(idx), v))
{
// Run this step concurrently across all platforms; short-circuit on first failure
let metadata = self.metadata;
let step_futs =
let step_futures =
self.platform_state
.iter_mut()
.map(|(node, platform_id, case_state)| {
let platform_id = *platform_id;
let node_ref = *node;
let step_clone = step.clone();
let step = step.clone();
let span = info_span!(
"Handling Step",
%step_idx,
platform = %platform_id,
);
async move {
let step_path = StepPath::from_iterator([step_idx]);
case_state
.handle_step(metadata, &step_clone, node_ref)
.handle_step(metadata, &step, &step_path, node_ref)
.await
.map_err(|e| (platform_id, e))
}
.instrument(span)
});
match try_join_all(step_futs).await {
match try_join_all(step_futures).await {
Ok(_outputs) => {
// All platforms succeeded for this step
steps_executed += 1;
}
Err((platform_id, error)) => {
+64 -2
View File
@@ -1,4 +1,4 @@
use std::{collections::HashMap, fmt::Display};
use std::{collections::HashMap, fmt::Display, str::FromStr};
use alloy::{
eips::BlockNumberOrTag,
@@ -46,9 +46,71 @@ pub enum Step {
define_wrapper_type!(
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct StepIdx(usize) impl Display;
pub struct StepIdx(usize) impl Display, FromStr;
);
define_wrapper_type!(
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct StepPath(Vec<StepIdx>);
);
impl StepPath {
pub fn from_iterator(path: impl IntoIterator<Item = impl Into<StepIdx>>) -> Self {
Self(path.into_iter().map(|value| value.into()).collect())
}
pub fn increment(&self) -> Self {
let mut this = self.clone();
if let Some(last) = this.last_mut() {
last.0 += 1
}
this
}
pub fn append(&self, step_idx: impl Into<StepIdx>) -> Self {
let mut this = self.clone();
this.0.push(step_idx.into());
this
}
}
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)
}
}
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)
}
}
impl From<StepPath> for String {
fn from(value: StepPath) -> Self {
value.to_string()
}
}
impl TryFrom<String> for StepPath {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
/// This is an input step which is a transaction description that the framework translates into a
/// transaction and executes on the nodes.
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
+2 -2
View File
@@ -4,7 +4,7 @@ use std::{path::PathBuf, sync::Arc};
use revive_dt_common::{define_wrapper_type, types::PlatformIdentifier};
use revive_dt_compiler::Mode;
use revive_dt_format::{case::CaseIdx, steps::StepIdx};
use revive_dt_format::{case::CaseIdx, steps::StepPath};
use serde::{Deserialize, Serialize};
define_wrapper_type!(
@@ -33,5 +33,5 @@ pub struct ExecutionSpecifier {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StepExecutionSpecifier {
pub execution_specifier: Arc<ExecutionSpecifier>,
pub step_idx: StepIdx,
pub step_idx: StepPath,
}
+1 -1
View File
@@ -580,4 +580,4 @@
]
}
}
}
}