mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-25 16:17:59 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 372cd5c52b | |||
| e122bbd996 | |||
| 6313ccb9b5 | |||
| 6b2516f639 | |||
| d4869deb68 | |||
| 52b21f8982 | |||
| 13a5b5a7ee | |||
| b962d032b9 | |||
| 496bc9a0ec | |||
| 92fc7894c0 | |||
| d7f69449af | |||
| f0f59ad024 | |||
| ac0f4e0cf2 | |||
| 9e4f2e95f1 | |||
| 7aadd0a7f7 | |||
| 1a25c8e0ab | |||
| 01d8042841 | |||
| 8a05f8e6e8 | |||
| 9fc74aeea0 | |||
| 49cbc51546 |
Generated
-2
@@ -4467,8 +4467,6 @@ dependencies = [
|
|||||||
name = "revive-dt-common"
|
name = "revive-dt-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
|
||||||
"alloy-primitives",
|
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"moka",
|
"moka",
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ repository.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alloy = { workspace = true }
|
|
||||||
alloy-primitives = { workspace = true }
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
moka = { workspace = true, features = ["sync"] }
|
moka = { workspace = true, features = ["sync"] }
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
mod identifiers;
|
mod identifiers;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod private_key_allocator;
|
|
||||||
mod version_or_requirement;
|
mod version_or_requirement;
|
||||||
|
|
||||||
pub use identifiers::*;
|
pub use identifiers::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
pub use private_key_allocator::*;
|
|
||||||
pub use version_or_requirement::*;
|
pub use version_or_requirement::*;
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
use alloy::signers::local::PrivateKeySigner;
|
|
||||||
use alloy_primitives::U256;
|
|
||||||
use anyhow::{Result, bail};
|
|
||||||
|
|
||||||
/// This is a sequential private key allocator. When instantiated, it allocated private keys in
|
|
||||||
/// sequentially and in order until the maximum private key specified is reached.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct PrivateKeyAllocator {
|
|
||||||
/// The next private key to be returned by the allocator when requested.
|
|
||||||
next_private_key: U256,
|
|
||||||
|
|
||||||
/// The highest private key (exclusive) that can be returned by this allocator.
|
|
||||||
highest_private_key_exclusive: U256,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrivateKeyAllocator {
|
|
||||||
/// Creates a new instance of the private key allocator.
|
|
||||||
pub fn new(highest_private_key_exclusive: U256) -> Self {
|
|
||||||
Self {
|
|
||||||
next_private_key: U256::ZERO,
|
|
||||||
highest_private_key_exclusive,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a new private key and errors out if the maximum private key has been reached.
|
|
||||||
pub fn allocate(&mut self) -> Result<PrivateKeySigner> {
|
|
||||||
if self.next_private_key >= self.highest_private_key_exclusive {
|
|
||||||
bail!("Attempted to allocate a private key but failed since all have been allocated");
|
|
||||||
};
|
|
||||||
let private_key =
|
|
||||||
PrivateKeySigner::from_slice(self.next_private_key.to_be_bytes::<32>().as_slice())?;
|
|
||||||
self.next_private_key += U256::ONE;
|
|
||||||
Ok(private_key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -490,10 +490,6 @@ impl WalletConfiguration {
|
|||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highest_private_key_exclusive(&self) -> U256 {
|
|
||||||
U256::try_from(self.additional_keys).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize_private_key<S>(value: &PrivateKeySigner, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
|||||||
+75
-131
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use alloy::consensus::EMPTY_ROOT_HASH;
|
use alloy::consensus::EMPTY_ROOT_HASH;
|
||||||
use alloy::hex;
|
use alloy::hex;
|
||||||
@@ -18,27 +17,25 @@ use alloy::{
|
|||||||
primitives::Address,
|
primitives::Address,
|
||||||
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
rpc::types::{TransactionRequest, trace::geth::DiffMode},
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, bail};
|
use anyhow::Context as _;
|
||||||
use futures::{TryStreamExt, future::try_join_all};
|
use futures::{TryStreamExt, future::try_join_all};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use revive_dt_common::types::{PlatformIdentifier, PrivateKeyAllocator};
|
use revive_dt_common::types::PlatformIdentifier;
|
||||||
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
use revive_dt_format::traits::{ResolutionContext, ResolverApi};
|
||||||
use revive_dt_report::ExecutionSpecificReporter;
|
use revive_dt_report::ExecutionSpecificReporter;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use revive_dt_format::case::Case;
|
use revive_dt_format::case::Case;
|
||||||
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
use revive_dt_format::input::{
|
||||||
use revive_dt_format::steps::{
|
BalanceAssertion, Calldata, EtherValue, Expected, ExpectedOutput, Input, Method, StepIdx,
|
||||||
BalanceAssertionStep, Calldata, EtherValue, Expected, ExpectedOutput, FunctionCallStep, Method,
|
StorageEmptyAssertion,
|
||||||
StepIdx, StepPath, StorageEmptyAssertionStep,
|
|
||||||
};
|
};
|
||||||
use revive_dt_format::{metadata::Metadata, steps::Step};
|
use revive_dt_format::metadata::{ContractIdent, ContractInstance, ContractPathAndIdent};
|
||||||
|
use revive_dt_format::{input::Step, metadata::Metadata};
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tokio::try_join;
|
use tokio::try_join;
|
||||||
use tracing::{Instrument, info, info_span, instrument};
|
use tracing::{Instrument, info, info_span, instrument};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CaseState {
|
pub struct CaseState {
|
||||||
/// A map of all of the compiled contracts for the given metadata file.
|
/// A map of all of the compiled contracts for the given metadata file.
|
||||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
@@ -55,10 +52,6 @@ pub struct CaseState {
|
|||||||
|
|
||||||
/// The execution reporter.
|
/// The execution reporter.
|
||||||
execution_reporter: ExecutionSpecificReporter,
|
execution_reporter: ExecutionSpecificReporter,
|
||||||
|
|
||||||
/// The private key allocator used for this case state. This is an Arc Mutex to allow for the
|
|
||||||
/// state to be cloned and for all of the clones to refer to the same allocator.
|
|
||||||
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CaseState {
|
impl CaseState {
|
||||||
@@ -67,7 +60,6 @@ impl CaseState {
|
|||||||
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
compiled_contracts: HashMap<PathBuf, HashMap<String, (String, JsonAbi)>>,
|
||||||
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
deployed_contracts: HashMap<ContractInstance, (ContractIdent, Address, JsonAbi)>,
|
||||||
execution_reporter: ExecutionSpecificReporter,
|
execution_reporter: ExecutionSpecificReporter,
|
||||||
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
compiled_contracts,
|
compiled_contracts,
|
||||||
@@ -75,7 +67,6 @@ impl CaseState {
|
|||||||
variables: Default::default(),
|
variables: Default::default(),
|
||||||
compiler_version,
|
compiler_version,
|
||||||
execution_reporter,
|
execution_reporter,
|
||||||
private_key_allocator,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +74,6 @@ impl CaseState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
step: &Step,
|
step: &Step,
|
||||||
step_path: &StepPath,
|
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<StepOutput> {
|
) -> anyhow::Result<StepOutput> {
|
||||||
match step {
|
match step {
|
||||||
@@ -106,24 +96,6 @@ impl CaseState {
|
|||||||
.context("Failed to handle storage empty assertion step")?;
|
.context("Failed to handle storage empty assertion step")?;
|
||||||
Ok(StepOutput::StorageEmptyAssertion)
|
Ok(StepOutput::StorageEmptyAssertion)
|
||||||
}
|
}
|
||||||
Step::Repeat(repetition_step) => {
|
|
||||||
self.handle_repeat(
|
|
||||||
metadata,
|
|
||||||
repetition_step.repeat,
|
|
||||||
&repetition_step.steps,
|
|
||||||
step_path,
|
|
||||||
node,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to handle the repetition step")?;
|
|
||||||
Ok(StepOutput::Repetition)
|
|
||||||
}
|
|
||||||
Step::AllocateAccount(account_allocation) => {
|
|
||||||
self.handle_account_allocation(account_allocation.variable_name.as_str())
|
|
||||||
.await
|
|
||||||
.context("Failed to allocate account")?;
|
|
||||||
Ok(StepOutput::AccountAllocation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.inspect(|_| info!("Step Succeeded"))
|
.inspect(|_| info!("Step Succeeded"))
|
||||||
}
|
}
|
||||||
@@ -132,7 +104,7 @@ impl CaseState {
|
|||||||
pub async fn handle_input(
|
pub async fn handle_input(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
input: &FunctionCallStep,
|
input: &Input,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||||
let resolver = node.resolver().await?;
|
let resolver = node.resolver().await?;
|
||||||
@@ -168,7 +140,7 @@ impl CaseState {
|
|||||||
pub async fn handle_balance_assertion(
|
pub async fn handle_balance_assertion(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
balance_assertion: &BalanceAssertionStep,
|
balance_assertion: &BalanceAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
self.handle_balance_assertion_contract_deployment(metadata, balance_assertion, node)
|
||||||
@@ -184,7 +156,7 @@ impl CaseState {
|
|||||||
pub async fn handle_storage_empty(
|
pub async fn handle_storage_empty(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
storage_empty: &StorageEmptyAssertionStep,
|
storage_empty: &StorageEmptyAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
self.handle_storage_empty_assertion_contract_deployment(metadata, storage_empty, node)
|
||||||
@@ -196,50 +168,12 @@ impl CaseState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", name = "Handling Repetition", skip_all)]
|
|
||||||
pub async fn handle_repeat(
|
|
||||||
&mut self,
|
|
||||||
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_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>(())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try_join_all(tasks).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "info", name = "Handling Account Allocation", skip_all)]
|
|
||||||
pub async fn handle_account_allocation(&mut self, variable_name: &str) -> anyhow::Result<()> {
|
|
||||||
let Some(variable_name) = variable_name.strip_prefix("$VARIABLE:") else {
|
|
||||||
bail!("Account allocation must start with $VARIABLE:");
|
|
||||||
};
|
|
||||||
|
|
||||||
let private_key = self.private_key_allocator.lock().await.allocate()?;
|
|
||||||
let account = private_key.address();
|
|
||||||
let variable = U256::from_be_slice(account.0.as_slice());
|
|
||||||
|
|
||||||
self.variables.insert(variable_name.to_string(), variable);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_contract_deployment(
|
async fn handle_input_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
input: &FunctionCallStep,
|
input: &Input,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
) -> anyhow::Result<HashMap<ContractInstance, TransactionReceipt>> {
|
||||||
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
let mut instances_we_must_deploy = IndexMap::<ContractInstance, bool>::new();
|
||||||
@@ -260,16 +194,15 @@ impl CaseState {
|
|||||||
.then_some(input.value)
|
.then_some(input.value)
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
let caller = {
|
|
||||||
let context = self.default_resolution_context();
|
|
||||||
let resolver = node.resolver().await?;
|
|
||||||
input
|
|
||||||
.caller
|
|
||||||
.resolve_address(resolver.as_ref(), context)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
if let (_, _, Some(receipt)) = self
|
if let (_, _, Some(receipt)) = self
|
||||||
.get_or_deploy_contract_instance(&instance, metadata, caller, calldata, value, node)
|
.get_or_deploy_contract_instance(
|
||||||
|
&instance,
|
||||||
|
metadata,
|
||||||
|
input.caller,
|
||||||
|
calldata,
|
||||||
|
value,
|
||||||
|
node,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to get or deploy contract instance during input execution")?
|
.context("Failed to get or deploy contract instance during input execution")?
|
||||||
{
|
{
|
||||||
@@ -284,7 +217,7 @@ impl CaseState {
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_execution(
|
async fn handle_input_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &FunctionCallStep,
|
input: &Input,
|
||||||
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<TransactionReceipt> {
|
) -> anyhow::Result<TransactionReceipt> {
|
||||||
@@ -348,7 +281,7 @@ impl CaseState {
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
fn handle_input_variable_assignment(
|
fn handle_input_variable_assignment(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &FunctionCallStep,
|
input: &Input,
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(ref assignments) = input.variable_assignments else {
|
let Some(ref assignments) = input.variable_assignments else {
|
||||||
@@ -379,26 +312,26 @@ impl CaseState {
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
async fn handle_input_expectations(
|
async fn handle_input_expectations(
|
||||||
&self,
|
&self,
|
||||||
input: &FunctionCallStep,
|
input: &Input,
|
||||||
execution_receipt: &TransactionReceipt,
|
execution_receipt: &TransactionReceipt,
|
||||||
resolver: &(impl ResolverApi + ?Sized),
|
resolver: &(impl ResolverApi + ?Sized),
|
||||||
tracing_result: &CallFrame,
|
tracing_result: &CallFrame,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
||||||
let mut expectations = match input {
|
let mut expectations = match input {
|
||||||
FunctionCallStep {
|
Input {
|
||||||
expected: Some(Expected::Calldata(calldata)),
|
expected: Some(Expected::Calldata(calldata)),
|
||||||
..
|
..
|
||||||
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
|
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
|
||||||
FunctionCallStep {
|
Input {
|
||||||
expected: Some(Expected::Expected(expected)),
|
expected: Some(Expected::Expected(expected)),
|
||||||
..
|
..
|
||||||
} => vec![expected.clone()],
|
} => vec![expected.clone()],
|
||||||
FunctionCallStep {
|
Input {
|
||||||
expected: Some(Expected::ExpectedMany(expected)),
|
expected: Some(Expected::ExpectedMany(expected)),
|
||||||
..
|
..
|
||||||
} => expected.clone(),
|
} => expected.clone(),
|
||||||
FunctionCallStep { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
|
Input { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is a bit of a special case and we have to support it separately on it's own. If it's
|
// This is a bit of a special case and we have to support it separately on it's own. If it's
|
||||||
@@ -499,9 +432,13 @@ impl CaseState {
|
|||||||
{
|
{
|
||||||
// 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 = expected_address
|
let expected = Address::from_slice(
|
||||||
.resolve_address(resolver, resolution_context)
|
Calldata::new_compound([expected_address])
|
||||||
.await?;
|
.calldata(resolver, resolution_context)
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
);
|
||||||
let actual = actual_event.address();
|
let actual = actual_event.address();
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
@@ -595,20 +532,20 @@ impl CaseState {
|
|||||||
pub async fn handle_balance_assertion_contract_deployment(
|
pub async fn handle_balance_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
balance_assertion: &BalanceAssertionStep,
|
balance_assertion: &BalanceAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(address) = balance_assertion.address.as_resolvable_address() else {
|
let Some(instance) = balance_assertion
|
||||||
|
.address
|
||||||
|
.strip_suffix(".address")
|
||||||
|
.map(ContractInstance::new)
|
||||||
|
else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let Some(instance) = address.strip_suffix(".address").map(ContractInstance::new) else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
self.get_or_deploy_contract_instance(
|
self.get_or_deploy_contract_instance(
|
||||||
&instance,
|
&instance,
|
||||||
metadata,
|
metadata,
|
||||||
FunctionCallStep::default_caller_address(),
|
Input::default_caller(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
node,
|
node,
|
||||||
@@ -620,17 +557,21 @@ impl CaseState {
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_balance_assertion_execution(
|
pub async fn handle_balance_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
BalanceAssertionStep {
|
BalanceAssertion {
|
||||||
address,
|
address: address_string,
|
||||||
expected_balance: amount,
|
expected_balance: amount,
|
||||||
..
|
..
|
||||||
}: &BalanceAssertionStep,
|
}: &BalanceAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let resolver = node.resolver().await?;
|
let resolver = node.resolver().await?;
|
||||||
let address = address
|
let address = Address::from_slice(
|
||||||
.resolve_address(resolver.as_ref(), self.default_resolution_context())
|
Calldata::new_compound([address_string])
|
||||||
.await?;
|
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
);
|
||||||
|
|
||||||
let balance = node.balance_of(address).await?;
|
let balance = node.balance_of(address).await?;
|
||||||
|
|
||||||
@@ -642,7 +583,7 @@ impl CaseState {
|
|||||||
"Balance assertion failed - Expected {} but got {} for {} resolved to {}",
|
"Balance assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
expected,
|
expected,
|
||||||
actual,
|
actual,
|
||||||
address,
|
address_string,
|
||||||
address,
|
address,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -654,20 +595,20 @@ impl CaseState {
|
|||||||
pub async fn handle_storage_empty_assertion_contract_deployment(
|
pub async fn handle_storage_empty_assertion_contract_deployment(
|
||||||
&mut self,
|
&mut self,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
storage_empty_assertion: &StorageEmptyAssertionStep,
|
storage_empty_assertion: &StorageEmptyAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let Some(address) = storage_empty_assertion.address.as_resolvable_address() else {
|
let Some(instance) = storage_empty_assertion
|
||||||
|
.address
|
||||||
|
.strip_suffix(".address")
|
||||||
|
.map(ContractInstance::new)
|
||||||
|
else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let Some(instance) = address.strip_suffix(".address").map(ContractInstance::new) else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
self.get_or_deploy_contract_instance(
|
self.get_or_deploy_contract_instance(
|
||||||
&instance,
|
&instance,
|
||||||
metadata,
|
metadata,
|
||||||
FunctionCallStep::default_caller_address(),
|
Input::default_caller(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
node,
|
node,
|
||||||
@@ -679,17 +620,21 @@ impl CaseState {
|
|||||||
#[instrument(level = "info", skip_all)]
|
#[instrument(level = "info", skip_all)]
|
||||||
pub async fn handle_storage_empty_assertion_execution(
|
pub async fn handle_storage_empty_assertion_execution(
|
||||||
&mut self,
|
&mut self,
|
||||||
StorageEmptyAssertionStep {
|
StorageEmptyAssertion {
|
||||||
address,
|
address: address_string,
|
||||||
is_storage_empty,
|
is_storage_empty,
|
||||||
..
|
..
|
||||||
}: &StorageEmptyAssertionStep,
|
}: &StorageEmptyAssertion,
|
||||||
node: &dyn EthereumNode,
|
node: &dyn EthereumNode,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let resolver = node.resolver().await?;
|
let resolver = node.resolver().await?;
|
||||||
let address = address
|
let address = Address::from_slice(
|
||||||
.resolve_address(resolver.as_ref(), self.default_resolution_context())
|
Calldata::new_compound([address_string])
|
||||||
.await?;
|
.calldata(resolver.as_ref(), self.default_resolution_context())
|
||||||
|
.await?
|
||||||
|
.get(12..32)
|
||||||
|
.expect("Can't fail"),
|
||||||
|
);
|
||||||
|
|
||||||
let storage = node.latest_state_proof(address, Default::default()).await?;
|
let storage = node.latest_state_proof(address, Default::default()).await?;
|
||||||
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
|
let is_empty = storage.storage_hash == EMPTY_ROOT_HASH;
|
||||||
@@ -703,7 +648,7 @@ impl CaseState {
|
|||||||
"Storage Empty Assertion failed - Expected {} but got {} for {} resolved to {}",
|
"Storage Empty Assertion failed - Expected {} but got {} for {} resolved to {}",
|
||||||
expected,
|
expected,
|
||||||
actual,
|
actual,
|
||||||
address,
|
address_string,
|
||||||
address,
|
address,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -846,31 +791,32 @@ impl<'a> CaseDriver<'a> {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, v)| (StepIdx::new(idx), v))
|
.map(|(idx, v)| (StepIdx::new(idx), v))
|
||||||
{
|
{
|
||||||
|
// Run this step concurrently across all platforms; short-circuit on first failure
|
||||||
let metadata = self.metadata;
|
let metadata = self.metadata;
|
||||||
let step_futures =
|
let step_futs =
|
||||||
self.platform_state
|
self.platform_state
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|(node, platform_id, case_state)| {
|
.map(|(node, platform_id, case_state)| {
|
||||||
let platform_id = *platform_id;
|
let platform_id = *platform_id;
|
||||||
let node_ref = *node;
|
let node_ref = *node;
|
||||||
let step = step.clone();
|
let step_clone = step.clone();
|
||||||
let span = info_span!(
|
let span = info_span!(
|
||||||
"Handling Step",
|
"Handling Step",
|
||||||
%step_idx,
|
%step_idx,
|
||||||
platform = %platform_id,
|
platform = %platform_id,
|
||||||
);
|
);
|
||||||
async move {
|
async move {
|
||||||
let step_path = StepPath::from_iterator([step_idx]);
|
|
||||||
case_state
|
case_state
|
||||||
.handle_step(metadata, &step, &step_path, node_ref)
|
.handle_step(metadata, &step_clone, node_ref)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (platform_id, e))
|
.map_err(|e| (platform_id, e))
|
||||||
}
|
}
|
||||||
.instrument(span)
|
.instrument(span)
|
||||||
});
|
});
|
||||||
|
|
||||||
match try_join_all(step_futures).await {
|
match try_join_all(step_futs).await {
|
||||||
Ok(_outputs) => {
|
Ok(_outputs) => {
|
||||||
|
// All platforms succeeded for this step
|
||||||
steps_executed += 1;
|
steps_executed += 1;
|
||||||
}
|
}
|
||||||
Err((platform_id, error)) => {
|
Err((platform_id, error)) => {
|
||||||
@@ -895,6 +841,4 @@ pub enum StepOutput {
|
|||||||
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
FunctionCall(TransactionReceipt, GethTrace, DiffMode),
|
||||||
BalanceAssertion,
|
BalanceAssertion,
|
||||||
StorageEmptyAssertion,
|
StorageEmptyAssertion,
|
||||||
Repetition,
|
|
||||||
AccountAllocation,
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-19
@@ -26,14 +26,10 @@ use revive_dt_report::{
|
|||||||
};
|
};
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tracing::{debug, error, info, info_span, instrument};
|
use tracing::{debug, error, info, info_span, instrument};
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
use revive_dt_common::{
|
use revive_dt_common::{iterators::EitherIter, types::Mode};
|
||||||
iterators::EitherIter,
|
|
||||||
types::{Mode, PrivateKeyAllocator},
|
|
||||||
};
|
|
||||||
use revive_dt_compiler::SolidityCompiler;
|
use revive_dt_compiler::SolidityCompiler;
|
||||||
use revive_dt_config::{Context, *};
|
use revive_dt_config::{Context, *};
|
||||||
use revive_dt_core::{
|
use revive_dt_core::{
|
||||||
@@ -43,9 +39,9 @@ use revive_dt_core::{
|
|||||||
use revive_dt_format::{
|
use revive_dt_format::{
|
||||||
case::{Case, CaseIdx},
|
case::{Case, CaseIdx},
|
||||||
corpus::Corpus,
|
corpus::Corpus,
|
||||||
|
input::{Input, Step},
|
||||||
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
metadata::{ContractPathAndIdent, Metadata, MetadataFile},
|
||||||
mode::ParsedMode,
|
mode::ParsedMode,
|
||||||
steps::{FunctionCallStep, Step},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cached_compiler::CachedCompiler;
|
use crate::cached_compiler::CachedCompiler;
|
||||||
@@ -330,13 +326,8 @@ async fn start_driver_task<'a>(
|
|||||||
.expect("Can't fail");
|
.expect("Can't fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
let private_key_allocator = Arc::new(Mutex::new(PrivateKeyAllocator::new(
|
|
||||||
context.wallet_configuration.highest_private_key_exclusive(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
let reporter = test.reporter.clone();
|
let reporter = test.reporter.clone();
|
||||||
let result =
|
let result = handle_case_driver(&test, cached_compiler).await;
|
||||||
handle_case_driver(&test, cached_compiler, private_key_allocator).await;
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(steps_executed) => reporter
|
Ok(steps_executed) => reporter
|
||||||
@@ -447,7 +438,6 @@ async fn start_cli_reporting_task(reporter: Reporter) {
|
|||||||
async fn handle_case_driver<'a>(
|
async fn handle_case_driver<'a>(
|
||||||
test: &Test<'a>,
|
test: &Test<'a>,
|
||||||
cached_compiler: Arc<CachedCompiler<'a>>,
|
cached_compiler: Arc<CachedCompiler<'a>>,
|
||||||
private_key_allocator: Arc<Mutex<PrivateKeyAllocator>>,
|
|
||||||
) -> anyhow::Result<usize> {
|
) -> anyhow::Result<usize> {
|
||||||
let platform_state = stream::iter(test.platforms.iter())
|
let platform_state = stream::iter(test.platforms.iter())
|
||||||
// Compiling the pre-link contracts.
|
// Compiling the pre-link contracts.
|
||||||
@@ -521,14 +511,12 @@ async fn handle_case_driver<'a>(
|
|||||||
.steps
|
.steps
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|step| match step {
|
.filter_map(|step| match step {
|
||||||
Step::FunctionCall(input) => input.caller.as_address().copied(),
|
Step::FunctionCall(input) => Some(input.caller),
|
||||||
Step::BalanceAssertion(..) => None,
|
Step::BalanceAssertion(..) => None,
|
||||||
Step::StorageEmptyAssertion(..) => None,
|
Step::StorageEmptyAssertion(..) => None,
|
||||||
Step::Repeat(..) => None,
|
|
||||||
Step::AllocateAccount(..) => None,
|
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or(FunctionCallStep::default_caller_address());
|
.unwrap_or(Input::default_caller());
|
||||||
let tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
let tx = TransactionBuilder::<Ethereum>::with_deploy_code(
|
||||||
TransactionRequest::default().from(deployer_address),
|
TransactionRequest::default().from(deployer_address),
|
||||||
code,
|
code,
|
||||||
@@ -575,7 +563,6 @@ async fn handle_case_driver<'a>(
|
|||||||
.filter_map(
|
.filter_map(
|
||||||
|(test, platform, node, compiler, reporter, _, deployed_libraries)| {
|
|(test, platform, node, compiler, reporter, _, deployed_libraries)| {
|
||||||
let cached_compiler = cached_compiler.clone();
|
let cached_compiler = cached_compiler.clone();
|
||||||
let private_key_allocator = private_key_allocator.clone();
|
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let compiler_output = cached_compiler
|
let compiler_output = cached_compiler
|
||||||
@@ -603,7 +590,6 @@ async fn handle_case_driver<'a>(
|
|||||||
compiler_output.contracts,
|
compiler_output.contracts,
|
||||||
deployed_libraries.unwrap_or_default(),
|
deployed_libraries.unwrap_or_default(),
|
||||||
reporter.clone(),
|
reporter.clone(),
|
||||||
private_key_allocator,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Some((*node, platform.platform_identifier(), case_state))
|
Some((*node, platform.platform_identifier(), case_state))
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
use revive_dt_common::{macros::define_wrapper_type, types::Mode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
input::{Expected, Step},
|
||||||
mode::ParsedMode,
|
mode::ParsedMode,
|
||||||
steps::{Expected, RepeatStep, Step},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, JsonSchema)]
|
||||||
@@ -55,6 +55,7 @@ pub struct Case {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Case {
|
impl Case {
|
||||||
|
#[allow(irrefutable_let_patterns)]
|
||||||
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
pub fn steps_iterator(&self) -> impl Iterator<Item = Step> {
|
||||||
let steps_len = self.steps.len();
|
let steps_len = self.steps.len();
|
||||||
self.steps
|
self.steps
|
||||||
@@ -83,24 +84,6 @@ impl Case {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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(..)));
|
|
||||||
if contains_repeat {
|
|
||||||
Box::new(self.steps_iterator()) as Box<_>
|
|
||||||
} else {
|
|
||||||
Box::new(std::iter::once(Step::Repeat(Box::new(RepeatStep {
|
|
||||||
comment: None,
|
|
||||||
repeat: default_repeat_count,
|
|
||||||
steps: self.steps_iterator().collect(),
|
|
||||||
})))) as Box<_>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn solc_modes(&self) -> Vec<Mode> {
|
pub fn solc_modes(&self) -> Vec<Mode> {
|
||||||
match &self.modes {
|
match &self.modes {
|
||||||
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
Some(modes) => ParsedMode::many_to_modes(modes.iter()).collect(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
eips::BlockNumberOrTag,
|
eips::BlockNumberOrTag,
|
||||||
@@ -28,104 +28,33 @@ use crate::{metadata::ContractInstance, traits::ResolutionContext};
|
|||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Step {
|
pub enum Step {
|
||||||
/// A function call or an invocation to some function on some smart contract.
|
/// A function call or an invocation to some function on some smart contract.
|
||||||
FunctionCall(Box<FunctionCallStep>),
|
FunctionCall(Box<Input>),
|
||||||
|
|
||||||
/// A step for performing a balance assertion on some account or contract.
|
/// A step for performing a balance assertion on some account or contract.
|
||||||
BalanceAssertion(Box<BalanceAssertionStep>),
|
BalanceAssertion(Box<BalanceAssertion>),
|
||||||
|
|
||||||
/// A step for asserting that the storage of some contract or account is empty.
|
/// A step for asserting that the storage of some contract or account is empty.
|
||||||
StorageEmptyAssertion(Box<StorageEmptyAssertionStep>),
|
StorageEmptyAssertion(Box<StorageEmptyAssertion>),
|
||||||
|
|
||||||
/// A special step for repeating a bunch of steps a certain number of times.
|
|
||||||
Repeat(Box<RepeatStep>),
|
|
||||||
|
|
||||||
/// A step type that allows for a new account address to be allocated and to later on be used
|
|
||||||
/// as the caller in another step.
|
|
||||||
AllocateAccount(Box<AllocateAccountStep>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct StepIdx(usize) impl Display, FromStr;
|
pub struct StepIdx(usize) impl Display;
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
/// This is an input step which is a transaction description that the framework translates into a
|
||||||
/// transaction and executes on the nodes.
|
/// transaction and executes on the nodes.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
pub struct FunctionCallStep {
|
pub struct Input {
|
||||||
/// The address of the account performing the call and paying the fees for it.
|
/// The address of the account performing the call and paying the fees for it.
|
||||||
#[serde(default = "FunctionCallStep::default_caller")]
|
#[serde(default = "Input::default_caller")]
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
pub caller: StepAddress,
|
pub caller: Address,
|
||||||
|
|
||||||
/// An optional comment on the step which has no impact on the execution in any way.
|
/// An optional comment on the step which has no impact on the execution in any way.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
|
|
||||||
/// The contract instance that's being called in this transaction step.
|
/// The contract instance that's being called in this transaction step.
|
||||||
#[serde(default = "FunctionCallStep::default_instance")]
|
#[serde(default = "Input::default_instance")]
|
||||||
pub instance: ContractInstance,
|
pub instance: ContractInstance,
|
||||||
|
|
||||||
/// The method that's being called in this step.
|
/// The method that's being called in this step.
|
||||||
@@ -155,8 +84,8 @@ pub struct FunctionCallStep {
|
|||||||
|
|
||||||
/// This represents a balance assertion step where the framework needs to query the balance of some
|
/// This represents a balance assertion step where the framework needs to query the balance of some
|
||||||
/// account or contract and assert that it's some amount.
|
/// account or contract and assert that it's some amount.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
pub struct BalanceAssertionStep {
|
pub struct BalanceAssertion {
|
||||||
/// An optional comment on the balance assertion.
|
/// An optional comment on the balance assertion.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
@@ -167,7 +96,7 @@ pub struct BalanceAssertionStep {
|
|||||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
/// 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
|
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||||
/// followed in the calldata.
|
/// followed in the calldata.
|
||||||
pub address: StepAddress,
|
pub address: String,
|
||||||
|
|
||||||
/// The amount of balance to assert that the account or contract has. This is a 256 bit string
|
/// The amount of balance to assert that the account or contract has. This is a 256 bit string
|
||||||
/// that's serialized and deserialized into a decimal string.
|
/// that's serialized and deserialized into a decimal string.
|
||||||
@@ -175,10 +104,8 @@ pub struct BalanceAssertionStep {
|
|||||||
pub expected_balance: U256,
|
pub expected_balance: U256,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents an assertion for the storage of some contract or account and whether it's empty
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
||||||
/// or not.
|
pub struct StorageEmptyAssertion {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
|
||||||
pub struct StorageEmptyAssertionStep {
|
|
||||||
/// An optional comment on the storage empty assertion.
|
/// An optional comment on the storage empty assertion.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
@@ -189,40 +116,12 @@ pub struct StorageEmptyAssertionStep {
|
|||||||
/// this could be a normal hex address, a variable such as `Test.address`, or perhaps even a
|
/// 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
|
/// full on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are
|
||||||
/// followed in the calldata.
|
/// followed in the calldata.
|
||||||
pub address: StepAddress,
|
pub address: String,
|
||||||
|
|
||||||
/// A boolean of whether the storage of the address is empty or not.
|
/// A boolean of whether the storage of the address is empty or not.
|
||||||
pub is_storage_empty: bool,
|
pub is_storage_empty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents a repetition step which is a special step type that allows for a sequence of
|
|
||||||
/// steps to be repeated (on different drivers) a certain number of times.
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
|
||||||
pub struct RepeatStep {
|
|
||||||
/// An optional comment on the repetition step.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub comment: Option<String>,
|
|
||||||
|
|
||||||
/// The number of repetitions that the steps should be repeated for.
|
|
||||||
pub repeat: usize,
|
|
||||||
|
|
||||||
/// The sequence of steps to repeat for the above defined number of repetitions.
|
|
||||||
pub steps: Vec<Step>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
|
||||||
pub struct AllocateAccountStep {
|
|
||||||
/// An optional comment on the account allocation step.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub comment: Option<String>,
|
|
||||||
|
|
||||||
/// An instruction to allocate a new account with the value being the variable name of that
|
|
||||||
/// account. This must start with `$VARIABLE:` and then be followed by the variable name of the
|
|
||||||
/// account.
|
|
||||||
#[serde(rename = "allocate_account")]
|
|
||||||
pub variable_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of expectations and assertions to make about the transaction after it ran.
|
/// A set of expectations and assertions to make about the transaction after it ran.
|
||||||
///
|
///
|
||||||
/// If this is not specified then the only assertion that will be ran is that the transaction
|
/// If this is not specified then the only assertion that will be ran is that the transaction
|
||||||
@@ -263,7 +162,7 @@ pub struct ExpectedOutput {
|
|||||||
pub struct Event {
|
pub struct Event {
|
||||||
/// An optional field of the address of the emitter of the event.
|
/// An optional field of the address of the emitter of the event.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub address: Option<StepAddress>,
|
pub address: Option<String>,
|
||||||
|
|
||||||
/// The set of topics to expect the event to have.
|
/// The set of topics to expect the event to have.
|
||||||
pub topics: Vec<String>,
|
pub topics: Vec<String>,
|
||||||
@@ -396,74 +295,13 @@ pub struct VariableAssignments {
|
|||||||
pub return_data: Vec<String>,
|
pub return_data: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An address type that might either be an address literal or a resolvable address.
|
impl Input {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, JsonSchema)]
|
pub const fn default_caller() -> Address {
|
||||||
#[schemars(with = "String")]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum StepAddress {
|
|
||||||
Address(Address),
|
|
||||||
ResolvableAddress(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StepAddress {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Address(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for StepAddress {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
StepAddress::Address(address) => Display::fmt(address, f),
|
|
||||||
StepAddress::ResolvableAddress(address) => Display::fmt(address, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StepAddress {
|
|
||||||
pub fn as_address(&self) -> Option<&Address> {
|
|
||||||
match self {
|
|
||||||
StepAddress::Address(address) => Some(address),
|
|
||||||
StepAddress::ResolvableAddress(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_resolvable_address(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
StepAddress::ResolvableAddress(address) => Some(address),
|
|
||||||
StepAddress::Address(..) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn resolve_address(
|
|
||||||
&self,
|
|
||||||
resolver: &(impl ResolverApi + ?Sized),
|
|
||||||
context: ResolutionContext<'_>,
|
|
||||||
) -> anyhow::Result<Address> {
|
|
||||||
match self {
|
|
||||||
StepAddress::Address(address) => Ok(*address),
|
|
||||||
StepAddress::ResolvableAddress(address) => Ok(Address::from_slice(
|
|
||||||
Calldata::new_compound([address])
|
|
||||||
.calldata(resolver, context)
|
|
||||||
.await?
|
|
||||||
.get(12..32)
|
|
||||||
.expect("Can't fail"),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionCallStep {
|
|
||||||
pub const fn default_caller_address() -> Address {
|
|
||||||
Address(FixedBytes(alloy::hex!(
|
Address(FixedBytes(alloy::hex!(
|
||||||
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
|
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn default_caller() -> StepAddress {
|
|
||||||
StepAddress::Address(Self::default_caller_address())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_instance() -> ContractInstance {
|
fn default_instance() -> ContractInstance {
|
||||||
ContractInstance::new("Test")
|
ContractInstance::new("Test")
|
||||||
}
|
}
|
||||||
@@ -546,8 +384,7 @@ impl FunctionCallStep {
|
|||||||
.encoded_input(resolver, context)
|
.encoded_input(resolver, context)
|
||||||
.await
|
.await
|
||||||
.context("Failed to encode input bytes for transaction request")?;
|
.context("Failed to encode input bytes for transaction request")?;
|
||||||
let caller = self.caller.resolve_address(resolver, context).await?;
|
let transaction_request = TransactionRequest::default().from(self.caller).value(
|
||||||
let transaction_request = TransactionRequest::default().from(caller).value(
|
|
||||||
self.value
|
self.value
|
||||||
.map(|value| value.into_inner())
|
.map(|value| value.into_inner())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -1053,7 +890,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input = FunctionCallStep {
|
let input = Input {
|
||||||
instance: ContractInstance::new("Contract"),
|
instance: ContractInstance::new("Contract"),
|
||||||
method: Method::FunctionName("store".to_owned()),
|
method: Method::FunctionName("store".to_owned()),
|
||||||
calldata: Calldata::new_compound(["42"]),
|
calldata: Calldata::new_compound(["42"]),
|
||||||
@@ -1097,7 +934,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input: FunctionCallStep = FunctionCallStep {
|
let input: Input = Input {
|
||||||
instance: "Contract".to_owned().into(),
|
instance: "Contract".to_owned().into(),
|
||||||
method: Method::FunctionName("send(address)".to_owned()),
|
method: Method::FunctionName("send(address)".to_owned()),
|
||||||
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
||||||
@@ -1144,7 +981,7 @@ mod tests {
|
|||||||
.selector()
|
.selector()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let input: FunctionCallStep = FunctionCallStep {
|
let input: Input = Input {
|
||||||
instance: ContractInstance::new("Contract"),
|
instance: ContractInstance::new("Contract"),
|
||||||
method: Method::FunctionName("send".to_owned()),
|
method: Method::FunctionName("send".to_owned()),
|
||||||
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
calldata: Calldata::new_compound(["0x1000000000000000000000000000000000000001"]),
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
pub mod case;
|
pub mod case;
|
||||||
pub mod corpus;
|
pub mod corpus;
|
||||||
|
pub mod input;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod steps;
|
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{path::PathBuf, sync::Arc};
|
|||||||
|
|
||||||
use revive_dt_common::{define_wrapper_type, types::PlatformIdentifier};
|
use revive_dt_common::{define_wrapper_type, types::PlatformIdentifier};
|
||||||
use revive_dt_compiler::Mode;
|
use revive_dt_compiler::Mode;
|
||||||
use revive_dt_format::{case::CaseIdx, steps::StepPath};
|
use revive_dt_format::{case::CaseIdx, input::StepIdx};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
define_wrapper_type!(
|
define_wrapper_type!(
|
||||||
@@ -33,5 +33,5 @@ pub struct ExecutionSpecifier {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct StepExecutionSpecifier {
|
pub struct StepExecutionSpecifier {
|
||||||
pub execution_specifier: Arc<ExecutionSpecifier>,
|
pub execution_specifier: Arc<ExecutionSpecifier>,
|
||||||
pub step_idx: StepPath,
|
pub step_idx: StepIdx,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ RUST_LOG="info" cargo run --release -- execute-tests \
|
|||||||
--corpus "$CORPUS_FILE" \
|
--corpus "$CORPUS_FILE" \
|
||||||
--working-directory "$WORKDIR" \
|
--working-directory "$WORKDIR" \
|
||||||
--concurrency.number-of-nodes 5 \
|
--concurrency.number-of-nodes 5 \
|
||||||
|
--concurrency.ignore-concurrency-limit \
|
||||||
--kitchensink.path "$SUBSTRATE_NODE_BIN" \
|
--kitchensink.path "$SUBSTRATE_NODE_BIN" \
|
||||||
--revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \
|
--revive-dev-node.path "$REVIVE_DEV_NODE_BIN" \
|
||||||
--eth-rpc.path "$ETH_RPC_BIN" \
|
--eth-rpc.path "$ETH_RPC_BIN" \
|
||||||
|
|||||||
+12
-98
@@ -25,7 +25,7 @@
|
|||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/VmIdentifier"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cases": {
|
"cases": {
|
||||||
@@ -95,26 +95,6 @@
|
|||||||
"cases"
|
"cases"
|
||||||
],
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"VmIdentifier": {
|
|
||||||
"description": "An enum representing the identifiers of the supported VMs.",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "The ethereum virtual machine.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "evm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The EraVM virtual machine.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "eravm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Polkadot's PolaVM Risc-v based virtual machine.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "polkavm"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Case": {
|
"Case": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -188,27 +168,19 @@
|
|||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "A function call or an invocation to some function on some smart contract.",
|
"description": "A function call or an invocation to some function on some smart contract.",
|
||||||
"$ref": "#/$defs/FunctionCallStep"
|
"$ref": "#/$defs/Input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A step for performing a balance assertion on some account or contract.",
|
"description": "A step for performing a balance assertion on some account or contract.",
|
||||||
"$ref": "#/$defs/BalanceAssertionStep"
|
"$ref": "#/$defs/BalanceAssertion"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A step for asserting that the storage of some contract or account is empty.",
|
"description": "A step for asserting that the storage of some contract or account is empty.",
|
||||||
"$ref": "#/$defs/StorageEmptyAssertionStep"
|
"$ref": "#/$defs/StorageEmptyAssertion"
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A special step for repeating a bunch of steps a certain number of times.",
|
|
||||||
"$ref": "#/$defs/RepeatStep"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A step type that allows for a new account address to be allocated and to later on be used\nas the caller in another step.",
|
|
||||||
"$ref": "#/$defs/AllocateAccountStep"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"FunctionCallStep": {
|
"Input": {
|
||||||
"description": "This is an input step which is a transaction description that the framework translates into a\ntransaction and executes on the nodes.",
|
"description": "This is an input step which is a transaction description that the framework translates into a\ntransaction and executes on the nodes.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -381,13 +353,9 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
"description": "An optional field of the address of the emitter of the event.",
|
"description": "An optional field of the address of the emitter of the event.",
|
||||||
"anyOf": [
|
"type": [
|
||||||
{
|
"string",
|
||||||
"$ref": "#/$defs/StepAddress"
|
"null"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"topics": {
|
"topics": {
|
||||||
@@ -407,10 +375,6 @@
|
|||||||
"values"
|
"values"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"StepAddress": {
|
|
||||||
"description": "An address type that might either be an address literal or a resolvable address.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"EtherValue": {
|
"EtherValue": {
|
||||||
"description": "Defines an Ether value.\n\nThis is an unsigned 256 bit integer that's followed by some denomination which can either be\neth, ether, gwei, or wei.",
|
"description": "Defines an Ether value.\n\nThis is an unsigned 256 bit integer that's followed by some denomination which can either be\neth, ether, gwei, or wei.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -430,7 +394,7 @@
|
|||||||
"return_data"
|
"return_data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"BalanceAssertionStep": {
|
"BalanceAssertion": {
|
||||||
"description": "This represents a balance assertion step where the framework needs to query the balance of some\naccount or contract and assert that it's some amount.",
|
"description": "This represents a balance assertion step where the framework needs to query the balance of some\naccount or contract and assert that it's some amount.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -443,7 +407,7 @@
|
|||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
||||||
"$ref": "#/$defs/StepAddress"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"expected_balance": {
|
"expected_balance": {
|
||||||
"description": "The amount of balance to assert that the account or contract has. This is a 256 bit string\nthat's serialized and deserialized into a decimal string.",
|
"description": "The amount of balance to assert that the account or contract has. This is a 256 bit string\nthat's serialized and deserialized into a decimal string.",
|
||||||
@@ -455,8 +419,7 @@
|
|||||||
"expected_balance"
|
"expected_balance"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"StorageEmptyAssertionStep": {
|
"StorageEmptyAssertion": {
|
||||||
"description": "This represents an assertion for the storage of some contract or account and whether it's empty\nor not.",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
@@ -468,7 +431,7 @@
|
|||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
"description": "The address that the balance assertion should be done on.\n\nThis is a string which will be resolved into an address when being processed. Therefore,\nthis could be a normal hex address, a variable such as `Test.address`, or perhaps even a\nfull on variable like `$VARIABLE:Uniswap`. It follows the same resolution rules that are\nfollowed in the calldata.",
|
||||||
"$ref": "#/$defs/StepAddress"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"is_storage_empty": {
|
"is_storage_empty": {
|
||||||
"description": "A boolean of whether the storage of the address is empty or not.",
|
"description": "A boolean of whether the storage of the address is empty or not.",
|
||||||
@@ -480,55 +443,6 @@
|
|||||||
"is_storage_empty"
|
"is_storage_empty"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"RepeatStep": {
|
|
||||||
"description": "This represents a repetition step which is a special step type that allows for a sequence of\nsteps to be repeated (on different drivers) a certain number of times.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"comment": {
|
|
||||||
"description": "An optional comment on the repetition step.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"repeat": {
|
|
||||||
"description": "The number of repetitions that the steps should be repeated for.",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"minimum": 0
|
|
||||||
},
|
|
||||||
"steps": {
|
|
||||||
"description": "The sequence of steps to repeat for the above defined number of repetitions.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/$defs/Step"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"repeat",
|
|
||||||
"steps"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"AllocateAccountStep": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"comment": {
|
|
||||||
"description": "An optional comment on the account allocation step.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"allocate_account": {
|
|
||||||
"description": "An instruction to allocate a new account with the value being the variable name of that\naccount. This must start with `$VARIABLE:` and then be followed by the variable name of the\naccount.",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"allocate_account"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ContractPathAndIdent": {
|
"ContractPathAndIdent": {
|
||||||
"description": "Represents an identifier used for contracts.\n\nThe type supports serialization from and into the following string format:\n\n```text\n${path}:${contract_ident}\n```",
|
"description": "Represents an identifier used for contracts.\n\nThe type supports serialization from and into the following string format:\n\n```text\n${path}:${contract_ident}\n```",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
Reference in New Issue
Block a user