mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-04-25 13:57:59 +00:00
Add support for exceptions
This commit is contained in:
+222
-37
@@ -5,8 +5,11 @@ use std::marker::PhantomData;
|
||||
|
||||
use alloy::json_abi::JsonAbi;
|
||||
use alloy::network::{Ethereum, TransactionBuilder};
|
||||
use alloy::primitives::Bytes;
|
||||
use alloy::rpc::types::TransactionReceipt;
|
||||
use alloy::rpc::types::trace::geth::GethTrace;
|
||||
use alloy::rpc::types::trace::geth::{
|
||||
DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace, PreStateConfig,
|
||||
};
|
||||
use alloy::{
|
||||
primitives::Address,
|
||||
rpc::types::{
|
||||
@@ -19,7 +22,7 @@ use indexmap::IndexMap;
|
||||
use revive_dt_compiler::{Compiler, SolidityCompiler};
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_format::case::CaseIdx;
|
||||
use revive_dt_format::input::Method;
|
||||
use revive_dt_format::input::{Calldata, Expected, ExpectedOutput, Method};
|
||||
use revive_dt_format::metadata::{ContractInstance, ContractPathAndIdentifier};
|
||||
use revive_dt_format::{input::Input, metadata::Metadata, mode::SolcMode};
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
@@ -145,7 +148,10 @@ where
|
||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||
let deployment_receipts =
|
||||
self.handle_contract_deployment(metadata, case_idx, input, node)?;
|
||||
self.handle_input_execution(case_idx, input, deployment_receipts, node)
|
||||
let execution_receipt =
|
||||
self.handle_input_execution(case_idx, input, deployment_receipts, node)?;
|
||||
self.handle_input_expectations(case_idx, input, &execution_receipt, node)?;
|
||||
self.handle_input_diff(case_idx, execution_receipt, node)
|
||||
}
|
||||
|
||||
/// Handles the contract deployment for a given input performing it if it needs to be performed.
|
||||
@@ -308,18 +314,15 @@ where
|
||||
&mut self,
|
||||
case_idx: CaseIdx,
|
||||
input: &Input,
|
||||
deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||
mut deployment_receipts: HashMap<ContractInstance, TransactionReceipt>,
|
||||
node: &T::Blockchain,
|
||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||
tracing::trace!("Calling execute_input for input: {input:?}");
|
||||
|
||||
let receipt = match input.method {
|
||||
) -> anyhow::Result<TransactionReceipt> {
|
||||
match input.method {
|
||||
// This input was already executed when `handle_input` was called. We just need to
|
||||
// lookup the transaction receipt in this case and continue on.
|
||||
Method::Deployer => deployment_receipts
|
||||
.get(&input.instance)
|
||||
.context("Failed to find deployment receipt")?
|
||||
.clone(),
|
||||
.remove(&input.instance)
|
||||
.context("Failed to find deployment receipt"),
|
||||
Method::Fallback | Method::FunctionName(_) => {
|
||||
let tx = match input
|
||||
.legacy_transaction(self.deployed_contracts.entry(case_idx).or_default(), node)
|
||||
@@ -337,35 +340,225 @@ where
|
||||
tracing::trace!("Executing transaction for input: {input:?}");
|
||||
|
||||
match node.execute_transaction(tx) {
|
||||
Ok(receipt) => receipt,
|
||||
Ok(receipt) => Ok(receipt),
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
"Failed to execute transaction when executing the contract: {}, {:?}",
|
||||
&*input.instance,
|
||||
err
|
||||
);
|
||||
return Err(err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_input_expectations(
|
||||
&mut self,
|
||||
case_idx: CaseIdx,
|
||||
input: &Input,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
) -> anyhow::Result<()> {
|
||||
let span = tracing::info_span!("Handling input expectations");
|
||||
let _guard = span.enter();
|
||||
|
||||
// Resolving the `input.expected` into a series of expectations that we can then assert on.
|
||||
let expectations = match input {
|
||||
// This is a bit of a special case and we have to support it separately on it's own. If
|
||||
// it's a call to the deployer method, then the tests will assert that it "returns" the
|
||||
// address of the contract. Deployments do not return the address of the contract but
|
||||
// the runtime code of the contracts. Therefore, this assertion would always fail. So,
|
||||
// we replace it with an assertion of "check if it succeeded"
|
||||
Input {
|
||||
expected: Some(Expected::Calldata(Calldata::Compound(compound))),
|
||||
method: Method::Deployer,
|
||||
..
|
||||
} if compound.len() == 1
|
||||
&& compound
|
||||
.first()
|
||||
.is_some_and(|first| first.contains(".address")) =>
|
||||
{
|
||||
vec![ExpectedOutput::new().with_success()]
|
||||
}
|
||||
Input {
|
||||
expected: Some(Expected::Calldata(calldata)),
|
||||
..
|
||||
} => vec![ExpectedOutput::new().with_calldata(calldata.clone())],
|
||||
Input {
|
||||
expected: Some(Expected::Expected(expected)),
|
||||
..
|
||||
} => vec![expected.clone()],
|
||||
Input {
|
||||
expected: Some(Expected::ExpectedMany(expected)),
|
||||
..
|
||||
} => expected.clone(),
|
||||
Input { expected: None, .. } => vec![ExpectedOutput::new().with_success()],
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
"Transaction receipt for executed contract: {} - {:?}",
|
||||
&*input.instance,
|
||||
receipt,
|
||||
);
|
||||
// Note: we need to do assertions and checks on the output of the last call and this isn't
|
||||
// available in the receipt. The only way to get this information is through tracing on the
|
||||
// node.
|
||||
let tracing_result = node
|
||||
.trace_transaction(
|
||||
execution_receipt,
|
||||
GethDebugTracingOptions {
|
||||
config: GethDefaultTracingOptions::default().with_enable_return_data(true),
|
||||
..Default::default()
|
||||
},
|
||||
)?
|
||||
.try_into_default_frame()
|
||||
.expect("Impossible. We can't request default tracing and get some other type back");
|
||||
|
||||
let trace = node.trace_transaction(receipt.clone())?;
|
||||
tracing::trace!(
|
||||
"Trace result for contract: {} - {:?}",
|
||||
&*input.instance,
|
||||
trace
|
||||
);
|
||||
for expectation in expectations.iter() {
|
||||
self.handle_input_expectation_item(
|
||||
case_idx,
|
||||
execution_receipt,
|
||||
node,
|
||||
expectation,
|
||||
&tracing_result,
|
||||
)?;
|
||||
}
|
||||
|
||||
let diff = node.state_diff(receipt.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok((receipt, trace, diff))
|
||||
fn handle_input_expectation_item(
|
||||
&mut self,
|
||||
case_idx: CaseIdx,
|
||||
execution_receipt: &TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
expectation: &ExpectedOutput,
|
||||
tracing_result: &DefaultFrame,
|
||||
) -> anyhow::Result<()> {
|
||||
// TODO: We want to respect the compiler version filter on the expected output but would
|
||||
// require some changes to the interfaces of the compiler and such. So, we add it later.
|
||||
// Additionally, what happens if the compiler filter doesn't match? Do we consider that the
|
||||
// transaction should succeed? Do we just ignore the expectation?
|
||||
|
||||
// Handling the receipt state assertion.
|
||||
let expected = !expectation.exception;
|
||||
let actual = execution_receipt.status();
|
||||
if actual != expected {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
expected,
|
||||
actual,
|
||||
"Transaction status assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Transaction status assertion failed - Expected {expected} but got {actual}",
|
||||
);
|
||||
}
|
||||
|
||||
// Handling the calldata assertion
|
||||
if let Some(ref expected_calldata) = expectation.return_data {
|
||||
let expected = expected_calldata
|
||||
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
|
||||
.map(Bytes::from)?;
|
||||
let actual = tracing_result.return_value.clone();
|
||||
if !expected.starts_with(&actual) {
|
||||
tracing::error!(?execution_receipt, %expected, %actual, "Calldata assertion failed");
|
||||
anyhow::bail!("Calldata assertion failed - Expected {expected} but got {actual}",);
|
||||
}
|
||||
}
|
||||
|
||||
// Handling the events assertion
|
||||
if let Some(ref expected_events) = expectation.events {
|
||||
// Handling the events length assertion.
|
||||
let expected = expected_events.len();
|
||||
let actual = execution_receipt.logs().len();
|
||||
if actual != expected {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
expected,
|
||||
actual,
|
||||
"Event count assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event count assertion failed - Expected {expected} but got {actual}",
|
||||
);
|
||||
}
|
||||
|
||||
// Handling the events assertion.
|
||||
for (expected_event, actual_event) in
|
||||
expected_events.iter().zip(execution_receipt.logs())
|
||||
{
|
||||
// Handling the emitter assertion.
|
||||
if let Some(expected_address) = expected_event.address {
|
||||
let expected = expected_address;
|
||||
let actual = actual_event.address();
|
||||
if actual != expected {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
%expected,
|
||||
%actual,
|
||||
"Event emitter assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event emitter assertion failed - Expected {expected} but got {actual}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handling the topics assertion.
|
||||
let expected = expected_event.topics.as_slice();
|
||||
let actual = actual_event.topics();
|
||||
if actual != expected {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
?actual,
|
||||
"Event topics assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event topics assertion failed - Expected {expected:?} but got {actual:?}",
|
||||
);
|
||||
}
|
||||
|
||||
// Handling the values assertion.
|
||||
let expected = &expected_event
|
||||
.values
|
||||
.calldata(self.deployed_contracts.entry(case_idx).or_default(), node)
|
||||
.map(Bytes::from)?;
|
||||
let actual = &actual_event.data().data;
|
||||
if !expected.starts_with(actual) {
|
||||
tracing::error!(
|
||||
?execution_receipt,
|
||||
?expected,
|
||||
?actual,
|
||||
"Event value assertion failed",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"Event value assertion failed - Expected {expected:?} but got {actual:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_input_diff(
|
||||
&mut self,
|
||||
_: CaseIdx,
|
||||
execution_receipt: TransactionReceipt,
|
||||
node: &T::Blockchain,
|
||||
) -> anyhow::Result<(TransactionReceipt, GethTrace, DiffMode)> {
|
||||
let span = tracing::info_span!("Handling input diff");
|
||||
let _guard = span.enter();
|
||||
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
disable_code: None,
|
||||
disable_storage: None,
|
||||
});
|
||||
|
||||
let trace = node.trace_transaction(&execution_receipt, trace_options)?;
|
||||
let diff = node.state_diff(&execution_receipt)?;
|
||||
|
||||
Ok((execution_receipt, trace, diff))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +698,7 @@ where
|
||||
|
||||
// For inputs if one of the inputs fail we move on to the next case (we do not move
|
||||
// on to the next input as it doesn't make sense. It depends on the previous one).
|
||||
for (input_idx, input) in case.inputs.iter().enumerate() {
|
||||
for (input_idx, input) in case.inputs_iterator().enumerate() {
|
||||
let tracing_span = tracing::info_span!("Handling input", input_idx);
|
||||
let _guard = tracing_span.enter();
|
||||
|
||||
@@ -513,7 +706,7 @@ where
|
||||
tracing::info_span!("Executing input", contract_name = ?input.instance)
|
||||
.in_scope(|| {
|
||||
let (leader_receipt, _, leader_diff) = match leader_state
|
||||
.handle_input(self.metadata, case_idx, input, self.leader_node)
|
||||
.handle_input(self.metadata, case_idx, &input, self.leader_node)
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
@@ -541,7 +734,7 @@ where
|
||||
.handle_input(
|
||||
self.metadata,
|
||||
case_idx,
|
||||
input,
|
||||
&input,
|
||||
self.follower_node,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
@@ -589,14 +782,6 @@ where
|
||||
tracing::trace!("Leader logs: {:?}", leader_receipt.logs());
|
||||
tracing::trace!("Follower logs: {:?}", follower_receipt.logs());
|
||||
}
|
||||
|
||||
if leader_receipt.status() != follower_receipt.status() {
|
||||
tracing::debug!(
|
||||
"Mismatch in status: leader = {}, follower = {}",
|
||||
leader_receipt.status(),
|
||||
follower_receipt.status()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Only consider the case as having been successful after we have processed
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{define_wrapper_type, input::Input, mode::Mode};
|
||||
use crate::{
|
||||
define_wrapper_type,
|
||||
input::{Expected, Input},
|
||||
mode::Mode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
pub struct Case {
|
||||
@@ -9,6 +13,7 @@ pub struct Case {
|
||||
pub modes: Option<Vec<Mode>>,
|
||||
pub inputs: Vec<Input>,
|
||||
pub group: Option<String>,
|
||||
pub expected: Option<Expected>,
|
||||
}
|
||||
|
||||
define_wrapper_type!(
|
||||
@@ -16,3 +21,21 @@ define_wrapper_type!(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
CaseIdx(usize);
|
||||
);
|
||||
|
||||
impl Case {
|
||||
pub fn inputs_iterator(&self) -> impl Iterator<Item = Input> {
|
||||
let inputs_len = self.inputs.len();
|
||||
self.inputs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, mut input)| {
|
||||
if idx + 1 == inputs_len {
|
||||
input.expected = self.expected.clone();
|
||||
input
|
||||
} else {
|
||||
input
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+54
-39
@@ -7,9 +7,9 @@ use alloy::{
|
||||
primitives::{Address, Bytes, U256},
|
||||
rpc::types::TransactionRequest,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use semver::VersionReq;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
|
||||
@@ -40,10 +40,18 @@ pub enum Expected {
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
pub struct ExpectedOutput {
|
||||
compiler_version: Option<VersionReq>,
|
||||
return_data: Option<Calldata>,
|
||||
events: Option<Value>,
|
||||
exception: Option<bool>,
|
||||
pub compiler_version: Option<VersionReq>,
|
||||
pub return_data: Option<Calldata>,
|
||||
pub events: Option<Vec<Event>>,
|
||||
#[serde(default)]
|
||||
pub exception: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
pub struct Event {
|
||||
pub address: Option<Address>,
|
||||
pub topics: Vec<B256>,
|
||||
pub values: Calldata,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
@@ -74,6 +82,27 @@ pub enum Method {
|
||||
FunctionName(String),
|
||||
}
|
||||
|
||||
impl ExpectedOutput {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_success(mut self) -> Self {
|
||||
self.exception = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_failure(mut self) -> Self {
|
||||
self.exception = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_calldata(mut self, calldata: Calldata) -> Self {
|
||||
self.return_data = Some(calldata);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Calldata {
|
||||
fn default() -> Self {
|
||||
Self::Compound(Default::default())
|
||||
@@ -91,7 +120,17 @@ impl Calldata {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct_call_data(
|
||||
pub fn calldata(
|
||||
&self,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
chain_state_provider: &impl EthereumNode,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let mut buffer = Vec::<u8>::with_capacity(self.size_requirement());
|
||||
self.calldata_into_slice(&mut buffer, deployed_contracts, chain_state_provider)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn calldata_into_slice(
|
||||
&self,
|
||||
buffer: &mut Vec<u8>,
|
||||
deployed_contracts: &HashMap<ContractInstance, (Address, JsonAbi)>,
|
||||
@@ -114,8 +153,8 @@ impl Calldata {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn size_requirement(&self) -> usize {
|
||||
@@ -126,14 +165,6 @@ impl Calldata {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpectedOutput {
|
||||
pub fn find_all_contract_instances(&self, vec: &mut Vec<ContractInstance>) {
|
||||
if let Some(ref cd) = self.return_data {
|
||||
cd.find_all_contract_instances(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
fn instance_to_address(
|
||||
&self,
|
||||
@@ -153,12 +184,9 @@ impl Input {
|
||||
) -> anyhow::Result<Bytes> {
|
||||
match self.method {
|
||||
Method::Deployer | Method::Fallback => {
|
||||
let mut calldata = Vec::<u8>::with_capacity(self.calldata.size_requirement());
|
||||
self.calldata.construct_call_data(
|
||||
&mut calldata,
|
||||
deployed_contracts,
|
||||
chain_state_provider,
|
||||
)?;
|
||||
let calldata = self
|
||||
.calldata
|
||||
.calldata(deployed_contracts, chain_state_provider)?;
|
||||
|
||||
Ok(calldata.into())
|
||||
}
|
||||
@@ -204,7 +232,7 @@ impl Input {
|
||||
// a new buffer for each one of the resolved arguments.
|
||||
let mut calldata = Vec::<u8>::with_capacity(4 + self.calldata.size_requirement());
|
||||
calldata.extend(function.selector().0);
|
||||
self.calldata.construct_call_data(
|
||||
self.calldata.calldata_into_slice(
|
||||
&mut calldata,
|
||||
deployed_contracts,
|
||||
chain_state_provider,
|
||||
@@ -236,20 +264,6 @@ impl Input {
|
||||
vec.push(self.instance.clone());
|
||||
|
||||
self.calldata.find_all_contract_instances(&mut vec);
|
||||
match &self.expected {
|
||||
Some(Expected::Calldata(cd)) => {
|
||||
cd.find_all_contract_instances(&mut vec);
|
||||
}
|
||||
Some(Expected::Expected(expected)) => {
|
||||
expected.find_all_contract_instances(&mut vec);
|
||||
}
|
||||
Some(Expected::ExpectedMany(expected)) => {
|
||||
for expected in expected {
|
||||
expected.find_all_contract_instances(&mut vec);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
@@ -355,14 +369,15 @@ mod tests {
|
||||
|
||||
fn trace_transaction(
|
||||
&self,
|
||||
_: alloy::rpc::types::TransactionReceipt,
|
||||
_: &alloy::rpc::types::TransactionReceipt,
|
||||
_: alloy::rpc::types::trace::geth::GethDebugTracingOptions,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn state_diff(
|
||||
&self,
|
||||
_: alloy::rpc::types::TransactionReceipt,
|
||||
_: &alloy::rpc::types::TransactionReceipt,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::DiffMode> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use alloy::eips::BlockNumberOrTag;
|
||||
use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256};
|
||||
use alloy::rpc::types::trace::geth::{DiffMode, GethTrace};
|
||||
use alloy::rpc::types::trace::geth::{DiffMode, GethDebugTracingOptions, GethTrace};
|
||||
use alloy::rpc::types::{TransactionReceipt, TransactionRequest};
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -15,10 +15,14 @@ pub trait EthereumNode {
|
||||
fn execute_transaction(&self, transaction: TransactionRequest) -> Result<TransactionReceipt>;
|
||||
|
||||
/// Trace the transaction in the [TransactionReceipt] and return a [GethTrace].
|
||||
fn trace_transaction(&self, transaction: TransactionReceipt) -> Result<GethTrace>;
|
||||
fn trace_transaction(
|
||||
&self,
|
||||
receipt: &TransactionReceipt,
|
||||
trace_options: GethDebugTracingOptions,
|
||||
) -> Result<GethTrace>;
|
||||
|
||||
/// Returns the state diff of the transaction hash in the [TransactionReceipt].
|
||||
fn state_diff(&self, transaction: TransactionReceipt) -> Result<DiffMode>;
|
||||
fn state_diff(&self, receipt: &TransactionReceipt) -> Result<DiffMode>;
|
||||
|
||||
/// Returns the next available nonce for the given [Address].
|
||||
fn fetch_add_nonce(&self, address: Address) -> Result<u64>;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use alloy::{
|
||||
network::{Network, TransactionBuilder},
|
||||
providers::{
|
||||
Provider, SendableTx,
|
||||
fillers::{GasFiller, TxFiller},
|
||||
},
|
||||
transports::TransportResult,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FallbackGasFiller {
|
||||
inner: GasFiller,
|
||||
default_gas_limit: u64,
|
||||
default_max_fee_per_gas: u128,
|
||||
default_priority_fee: u128,
|
||||
}
|
||||
|
||||
impl FallbackGasFiller {
|
||||
pub fn new(
|
||||
default_gas_limit: u64,
|
||||
default_max_fee_per_gas: u128,
|
||||
default_priority_fee: u128,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: GasFiller,
|
||||
default_gas_limit,
|
||||
default_max_fee_per_gas,
|
||||
default_priority_fee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> TxFiller<N> for FallbackGasFiller
|
||||
where
|
||||
N: Network,
|
||||
{
|
||||
type Fillable = Option<<GasFiller as TxFiller<N>>::Fillable>;
|
||||
|
||||
fn status(
|
||||
&self,
|
||||
tx: &<N as Network>::TransactionRequest,
|
||||
) -> alloy::providers::fillers::FillerControlFlow {
|
||||
<GasFiller as TxFiller<N>>::status(&self.inner, tx)
|
||||
}
|
||||
|
||||
fn fill_sync(&self, _: &mut alloy::providers::SendableTx<N>) {}
|
||||
|
||||
async fn prepare<P: Provider<N>>(
|
||||
&self,
|
||||
provider: &P,
|
||||
tx: &<N as Network>::TransactionRequest,
|
||||
) -> TransportResult<Self::Fillable> {
|
||||
// Try to fetch GasFiller’s “fillable” (gas_price, base_fee, estimate_gas, …)
|
||||
// If it errors (i.e. tx would revert under eth_estimateGas), swallow it.
|
||||
match self.inner.prepare(provider, tx).await {
|
||||
Ok(fill) => Ok(Some(fill)),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fill(
|
||||
&self,
|
||||
fillable: Self::Fillable,
|
||||
mut tx: alloy::providers::SendableTx<N>,
|
||||
) -> TransportResult<SendableTx<N>> {
|
||||
if let Some(fill) = fillable {
|
||||
// our inner GasFiller succeeded — use it
|
||||
self.inner.fill(fill, tx).await
|
||||
} else {
|
||||
if let Some(builder) = tx.as_mut_builder() {
|
||||
builder.set_gas_limit(self.default_gas_limit);
|
||||
builder.set_max_fee_per_gas(self.default_max_fee_per_gas);
|
||||
builder.set_max_priority_fee_per_gas(self.default_priority_fee);
|
||||
}
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-16
@@ -20,7 +20,7 @@ use alloy::{
|
||||
providers::{
|
||||
Provider, ProviderBuilder,
|
||||
ext::DebugApi,
|
||||
fillers::{FillProvider, TxFiller},
|
||||
fillers::{ChainIdFiller, FillProvider, NonceFiller, SimpleNonceManager, TxFiller},
|
||||
},
|
||||
rpc::types::{
|
||||
TransactionReceipt, TransactionRequest,
|
||||
@@ -31,7 +31,7 @@ use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, common::FallbackGasFiller};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -208,6 +208,10 @@ impl Instance {
|
||||
let wallet = self.wallet.clone();
|
||||
Box::pin(async move {
|
||||
ProviderBuilder::new()
|
||||
.disable_recommended_fillers()
|
||||
.filler(FallbackGasFiller::new(500_000_000, 500_000_000, 1))
|
||||
.filler(ChainIdFiller::default())
|
||||
.filler(NonceFiller::<SimpleNonceManager>::default())
|
||||
.wallet(wallet)
|
||||
.connect(&connection_string)
|
||||
.await
|
||||
@@ -224,7 +228,7 @@ impl EthereumNode for Instance {
|
||||
) -> anyhow::Result<alloy::rpc::types::TransactionReceipt> {
|
||||
let provider = self.provider();
|
||||
BlockingExecutor::execute(async move {
|
||||
let outer_span = tracing::debug_span!("Submitting transaction", ?transaction,);
|
||||
let outer_span = tracing::debug_span!("Submitting transaction", ?transaction);
|
||||
let _outer_guard = outer_span.enter();
|
||||
|
||||
let provider = provider.await?;
|
||||
@@ -305,30 +309,28 @@ impl EthereumNode for Instance {
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||
fn trace_transaction(
|
||||
&self,
|
||||
transaction: TransactionReceipt,
|
||||
transaction: &TransactionReceipt,
|
||||
trace_options: GethDebugTracingOptions,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
disable_code: None,
|
||||
disable_storage: None,
|
||||
});
|
||||
let tx_hash = transaction.transaction_hash;
|
||||
let provider = self.provider();
|
||||
|
||||
BlockingExecutor::execute(async move {
|
||||
Ok(provider
|
||||
.await?
|
||||
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
||||
.debug_trace_transaction(tx_hash, trace_options)
|
||||
.await?)
|
||||
})?
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(geth_node_id = self.id))]
|
||||
fn state_diff(
|
||||
&self,
|
||||
transaction: alloy::rpc::types::TransactionReceipt,
|
||||
) -> anyhow::Result<DiffMode> {
|
||||
fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
disable_code: None,
|
||||
disable_storage: None,
|
||||
});
|
||||
match self
|
||||
.trace_transaction(transaction)?
|
||||
.trace_transaction(transaction, trace_options)?
|
||||
.try_into_pre_state_frame()?
|
||||
{
|
||||
PreStateFrame::Diff(diff) => Ok(diff),
|
||||
|
||||
@@ -23,7 +23,7 @@ use alloy::{
|
||||
providers::{
|
||||
Provider, ProviderBuilder,
|
||||
ext::DebugApi,
|
||||
fillers::{FillProvider, TxFiller},
|
||||
fillers::{ChainIdFiller, FillProvider, NonceFiller, SimpleNonceManager, TxFiller},
|
||||
},
|
||||
rpc::types::{
|
||||
TransactionReceipt,
|
||||
@@ -40,7 +40,7 @@ use tracing::Level;
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::{BlockingExecutor, EthereumNode};
|
||||
|
||||
use crate::Node;
|
||||
use crate::{Node, common::FallbackGasFiller};
|
||||
|
||||
static NODE_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -352,6 +352,14 @@ impl KitchensinkNode {
|
||||
let wallet = self.wallet.clone();
|
||||
Box::pin(async move {
|
||||
ProviderBuilder::new()
|
||||
.disable_recommended_fillers()
|
||||
.filler(FallbackGasFiller::new(
|
||||
30_000_000,
|
||||
200_000_000_000,
|
||||
3_000_000_000,
|
||||
))
|
||||
.filler(ChainIdFiller::default())
|
||||
.filler(NonceFiller::<SimpleNonceManager>::default())
|
||||
.network::<KitchenSinkNetwork>()
|
||||
.wallet(wallet)
|
||||
.connect(&connection_string)
|
||||
@@ -384,27 +392,28 @@ impl EthereumNode for KitchensinkNode {
|
||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
||||
fn trace_transaction(
|
||||
&self,
|
||||
transaction: TransactionReceipt,
|
||||
transaction: &TransactionReceipt,
|
||||
trace_options: GethDebugTracingOptions,
|
||||
) -> anyhow::Result<alloy::rpc::types::trace::geth::GethTrace> {
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
disable_code: None,
|
||||
disable_storage: None,
|
||||
});
|
||||
let tx_hash = transaction.transaction_hash;
|
||||
let provider = self.provider();
|
||||
|
||||
BlockingExecutor::execute(async move {
|
||||
Ok(provider
|
||||
.await?
|
||||
.debug_trace_transaction(transaction.transaction_hash, trace_options)
|
||||
.debug_trace_transaction(tx_hash, trace_options)
|
||||
.await?)
|
||||
})?
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))]
|
||||
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||
fn state_diff(&self, transaction: &TransactionReceipt) -> anyhow::Result<DiffMode> {
|
||||
let trace_options = GethDebugTracingOptions::prestate_tracer(PreStateConfig {
|
||||
diff_mode: Some(true),
|
||||
disable_code: None,
|
||||
disable_storage: None,
|
||||
});
|
||||
match self
|
||||
.trace_transaction(transaction)?
|
||||
.trace_transaction(transaction, trace_options)?
|
||||
.try_into_pre_state_frame()?
|
||||
{
|
||||
PreStateFrame::Diff(diff) => Ok(diff),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use revive_dt_config::Arguments;
|
||||
use revive_dt_node_interaction::EthereumNode;
|
||||
|
||||
pub mod common;
|
||||
pub mod geth;
|
||||
pub mod kitchensink;
|
||||
pub mod pool;
|
||||
|
||||
Reference in New Issue
Block a user