refactor: move brother_node development artifact to dev/test-nodes subdirectory

Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
2026-03-30 17:09:06 +02:00
parent bf730dcb4a
commit 816e258d4c
11734 changed files with 2001707 additions and 0 deletions

124
dev/env/node_modules/@nomicfoundation/edr/src/account.rs generated vendored Executable file
View File

@@ -0,0 +1,124 @@
use derive_more::Debug;
use edr_primitives::{hex, Address, HashMap, U256};
use edr_solidity_tests::{backend::Predeploy, revm::state::AccountInfo};
use edr_state_api::EvmStorageSlot;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
use crate::{
cast::TryCast,
serde::{
serialize_bigint_as_struct, serialize_optional_bigint_as_struct,
serialize_optional_uint8array_as_hex, serialize_uint8array_as_hex,
},
};
/// Specification of overrides for an account and its storage.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct AccountOverride {
/// The account's address
#[debug("{}", hex::encode(address))]
#[serde(serialize_with = "serialize_uint8array_as_hex")]
pub address: Uint8Array,
/// If present, the overwriting balance.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub balance: Option<BigInt>,
/// If present, the overwriting nonce.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub nonce: Option<BigInt>,
/// If present, the overwriting code.
#[debug("{:?}", code.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub code: Option<Uint8Array>,
/// BEWARE: This field is not supported yet. See <https://github.com/NomicFoundation/edr/issues/911>
///
/// If present, the overwriting storage.
pub storage: Option<Vec<StorageSlot>>,
}
impl TryFrom<AccountOverride> for (Address, edr_provider::AccountOverride) {
type Error = napi::Error;
fn try_from(value: AccountOverride) -> Result<Self, Self::Error> {
let AccountOverride {
address,
balance,
nonce,
code,
storage,
} = value;
let storage = storage
.map(|storage| {
storage
.into_iter()
.map(|StorageSlot { index, value }| {
let value = value.try_cast()?;
let slot = EvmStorageSlot::new(value, 0);
let index: U256 = index.try_cast()?;
Ok((index, slot))
})
.collect::<napi::Result<_>>()
})
.transpose()?;
let account_override = edr_provider::AccountOverride {
balance: balance.map(TryCast::try_cast).transpose()?,
nonce: nonce.map(TryCast::try_cast).transpose()?,
code: code.map(TryCast::try_cast).transpose()?,
storage,
};
let address: Address = address.try_cast()?;
Ok((address, account_override))
}
}
impl TryFrom<AccountOverride> for Predeploy {
type Error = napi::Error;
fn try_from(value: AccountOverride) -> Result<Self, Self::Error> {
let (address, account_override) = value.try_into()?;
let storage = account_override.storage.unwrap_or_else(HashMap::default);
let balance = account_override.balance.unwrap_or(U256::ZERO);
let nonce = account_override.nonce.unwrap_or(0);
let code = account_override.code.ok_or_else(|| {
napi::Error::from_reason(format!("Predeploy with address '{address}' must have code"))
})?;
if code.is_empty() {
return Err(napi::Error::from_reason(
"Predeploy with address '{address}' must have non-empty code",
));
}
let code_hash = code.hash_slow();
let account_info = AccountInfo {
balance,
nonce,
code_hash,
code: Some(code),
};
Ok(Self {
address,
account_info,
storage,
})
}
}
/// A description of a storage slot's state.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct StorageSlot {
/// The storage slot's index
#[serde(serialize_with = "serialize_bigint_as_struct")]
pub index: BigInt,
/// The storage slot's value
#[serde(serialize_with = "serialize_bigint_as_struct")]
pub value: BigInt,
}

28
dev/env/node_modules/@nomicfoundation/edr/src/block.rs generated vendored Executable file
View File

@@ -0,0 +1,28 @@
use napi::bindgen_prelude::BigInt;
use napi_derive::napi;
use crate::cast::TryCast;
/// Information about the blob gas used in a block.
#[napi(object)]
pub struct BlobGas {
/// The total amount of blob gas consumed by the transactions within the
/// block.
pub gas_used: BigInt,
/// The running total of blob gas consumed in excess of the target, prior to
/// the block. Blocks with above-target blob gas consumption increase this
/// value, blocks with below-target blob gas consumption decrease it
/// (bounded at 0).
pub excess_gas: BigInt,
}
impl TryFrom<BlobGas> for edr_block_header::BlobGas {
type Error = napi::Error;
fn try_from(value: BlobGas) -> Result<Self, Self::Error> {
Ok(Self {
gas_used: BigInt::try_cast(value.gas_used)?,
excess_gas: BigInt::try_cast(value.excess_gas)?,
})
}
}

View File

@@ -0,0 +1,116 @@
use std::sync::mpsc::channel;
use edr_primitives::{Address, Bytes};
use napi::{
bindgen_prelude::{Promise, Uint8Array},
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::runtime,
Env, JsFunction, Status,
};
use napi_derive::napi;
use crate::cast::TryCast;
/// The result of executing a call override.
#[napi(object)]
pub struct CallOverrideResult {
pub result: Uint8Array,
pub should_revert: bool,
}
impl TryCast<Option<edr_provider::CallOverrideResult>> for Option<CallOverrideResult> {
type Error = napi::Error;
fn try_cast(self) -> Result<Option<edr_provider::CallOverrideResult>, Self::Error> {
match self {
None => Ok(None),
Some(result) => Ok(Some(edr_provider::CallOverrideResult {
output: Bytes::copy_from_slice(&result.result),
should_revert: result.should_revert,
})),
}
}
}
struct CallOverrideCall {
contract_address: Address,
data: Bytes,
}
#[derive(Clone)]
pub struct CallOverrideCallback {
call_override_callback_fn: ThreadsafeFunction<CallOverrideCall, ErrorStrategy::Fatal>,
runtime: runtime::Handle,
}
impl CallOverrideCallback {
pub fn new(
env: &Env,
call_override_callback: JsFunction,
runtime: runtime::Handle,
) -> napi::Result<Self> {
let mut call_override_callback_fn = call_override_callback.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<CallOverrideCall>| {
let address = ctx
.env
.create_arraybuffer_with_data(ctx.value.contract_address.to_vec())?
.into_raw();
let data = ctx
.env
.create_arraybuffer_with_data(ctx.value.data.to_vec())?
.into_raw();
Ok(vec![address, data])
},
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
call_override_callback_fn.unref(env)?;
Ok(Self {
call_override_callback_fn,
runtime,
})
}
pub fn call_override(
&self,
contract_address: Address,
data: Bytes,
) -> Option<edr_provider::CallOverrideResult> {
let (sender, receiver) = channel();
let runtime = self.runtime.clone();
let status = self.call_override_callback_fn.call_with_return_value(
CallOverrideCall {
contract_address,
data,
},
ThreadsafeFunctionCallMode::Blocking,
move |result: Promise<Option<CallOverrideResult>>| {
runtime.spawn(async move {
let result = result.await?.try_cast();
sender.send(result).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from call_override_callback",
)
})
});
Ok(())
},
);
assert_eq!(status, Status::Ok, "Call override callback failed");
receiver
.recv()
.unwrap()
.expect("Failed call to call_override_callback")
}
}

165
dev/env/node_modules/@nomicfoundation/edr/src/cast.rs generated vendored Executable file
View File

@@ -0,0 +1,165 @@
use edr_primitives::{Address, Bytecode, Bytes, B256, B64, U256};
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
Status,
};
/// An attempted conversion that consumes `self`, which may or may not be
/// expensive. It is identical to [`TryInto`], but it allows us to implement
/// the trait for external types.
pub trait TryCast<T>: Sized {
/// The type returned in the event of a conversion error.
type Error;
/// Performs the conversion.
fn try_cast(self) -> Result<T, Self::Error>;
}
impl TryCast<Address> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<Address, Self::Error> {
if self.len() != 20 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 20 bytes.".to_string(),
));
}
Ok(Address::from_slice(&self))
}
}
impl TryCast<B64> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<B64, Self::Error> {
if self.len() != 8 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 8 bytes.".to_string(),
));
}
Ok(B64::from_slice(&self))
}
}
impl TryCast<B256> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<B256, Self::Error> {
if self.len() != 32 {
return Err(napi::Error::new(
Status::InvalidArg,
"Uint8Array was expected to be 32 bytes.".to_string(),
));
}
Ok(B256::from_slice(&self))
}
}
impl TryCast<Bytecode> for Uint8Array {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<Bytecode, Self::Error> {
let bytes = Bytes::copy_from_slice(&self);
Bytecode::new_raw_checked(bytes).map_err(|error| {
napi::Error::new(
Status::InvalidArg,
format!("Uint8Array was not valid bytecode: {error}"),
)
})
}
}
impl TryCast<u64> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<u64, Self::Error> {
let (signed, value, lossless) = self.get_u64();
if signed {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to be unsigned.".to_string(),
));
}
if !lossless {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to fit within 64 bits.".to_string(),
));
}
Ok(value)
}
}
impl TryCast<u128> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<u128, Self::Error> {
let (signed, value, lossless) = self.get_u128();
if signed {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to be unsigned.".to_string(),
));
}
if !lossless {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt was expected to fit within 128 bits.".to_string(),
));
}
Ok(value)
}
}
impl TryCast<usize> for BigInt {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<usize, Self::Error> {
let size: u64 = BigInt::try_cast(self)?;
usize::try_from(size).map_err(|e| napi::Error::new(Status::InvalidArg, e.to_string()))
}
}
impl TryCast<U256> for BigInt {
type Error = napi::Error;
fn try_cast(mut self) -> std::result::Result<U256, Self::Error> {
let num_words = self.words.len();
match num_words.cmp(&4) {
std::cmp::Ordering::Less => self.words.append(&mut vec![0u64; 4 - num_words]),
std::cmp::Ordering::Equal => (),
std::cmp::Ordering::Greater => {
return Err(napi::Error::new(
Status::InvalidArg,
"BigInt cannot have more than 4 words.".to_owned(),
));
}
}
Ok(U256::from_limbs(self.words.try_into().unwrap()))
}
}
impl<T> TryCast<T> for T {
type Error = napi::Error;
fn try_cast(self) -> std::result::Result<T, Self::Error> {
Ok(self)
}
}
impl TryCast<Option<Bytes>> for Option<Uint8Array> {
type Error = napi::Error;
fn try_cast(self) -> Result<Option<Bytes>, Self::Error> {
Ok(self.map(|buffer| Bytes::copy_from_slice(&buffer)))
}
}

7
dev/env/node_modules/@nomicfoundation/edr/src/chains.rs generated vendored Executable file
View File

@@ -0,0 +1,7 @@
/// Types for the generic L1 Ethereum implementation.
pub mod generic;
/// Types for L1 Ethereum implementation.
pub mod l1;
/// Types for OP implementation.
#[cfg(feature = "op")]
pub mod op;

View File

@@ -0,0 +1,58 @@
use std::sync::Arc;
use edr_generic::GenericChainSpec;
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::tokio::runtime;
use napi_derive::napi;
use crate::provider::ProviderFactory;
pub struct GenericChainProviderFactory;
impl SyncProviderFactory for GenericChainProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger = Logger::<GenericChainSpec, CurrentTime>::new(
logger_config,
Arc::clone(&contract_decoder),
)?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<GenericChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<GenericChainSpec, CurrentTime>(
subscription_callback,
),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
#[napi]
pub const GENERIC_CHAIN_TYPE: &str = edr_generic::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn generic_chain_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(GenericChainProviderFactory);
factory.into()
}

274
dev/env/node_modules/@nomicfoundation/edr/src/chains/l1.rs generated vendored Executable file
View File

@@ -0,0 +1,274 @@
use std::{str::FromStr, sync::Arc};
use edr_blockchain_fork::eips::{
eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_UNSUPPORTED_BYTECODE},
eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_BYTECODE},
};
use edr_chain_l1::L1ChainSpec;
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
};
use napi_derive::napi;
use crate::{account::AccountOverride, provider::ProviderFactory};
pub struct L1ProviderFactory;
impl SyncProviderFactory for L1ProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger =
Logger::<L1ChainSpec, CurrentTime>::new(logger_config, Arc::clone(&contract_decoder))?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<L1ChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<L1ChainSpec, CurrentTime>(subscription_callback),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
#[napi]
pub const L1_CHAIN_TYPE: &str = edr_chain_l1::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn l1_genesis_state(hardfork: SpecId) -> Vec<AccountOverride> {
// Use closures for lazy execution
let beacon_roots_account_constructor = || AccountOverride {
address: Uint8Array::with_data_copied(BEACON_ROOTS_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(Uint8Array::with_data_copied(&BEACON_ROOTS_BYTECODE)),
storage: Some(Vec::new()),
};
let history_storage_account_constructor = || AccountOverride {
address: Uint8Array::with_data_copied(HISTORY_STORAGE_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(Uint8Array::with_data_copied(
&HISTORY_STORAGE_UNSUPPORTED_BYTECODE,
)),
storage: Some(Vec::new()),
};
if hardfork < SpecId::Cancun {
Vec::new()
} else if hardfork < SpecId::Prague {
vec![beacon_roots_account_constructor()]
} else {
vec![
beacon_roots_account_constructor(),
history_storage_account_constructor(),
]
}
}
#[napi(catch_unwind)]
pub fn l1_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(L1ProviderFactory);
factory.into()
}
/// Identifier for the Ethereum spec.
#[napi]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum SpecId {
/// Frontier
Frontier = 0,
/// Frontier Thawing
FrontierThawing = 1,
/// Homestead
Homestead = 2,
/// DAO Fork
DaoFork = 3,
/// Tangerine
Tangerine = 4,
/// Spurious Dragon
SpuriousDragon = 5,
/// Byzantium
Byzantium = 6,
/// Constantinople
Constantinople = 7,
/// Petersburg
Petersburg = 8,
/// Istanbul
Istanbul = 9,
/// Muir Glacier
MuirGlacier = 10,
/// Berlin
Berlin = 11,
/// London
London = 12,
/// Arrow Glacier
ArrowGlacier = 13,
/// Gray Glacier
GrayGlacier = 14,
/// Merge
Merge = 15,
/// Shanghai
Shanghai = 16,
/// Cancun
Cancun = 17,
/// Prague
Prague = 18,
/// Osaka
Osaka = 19,
}
impl FromStr for SpecId {
type Err = napi::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
edr_chain_l1::chains::name::FRONTIER => Ok(SpecId::Frontier),
edr_chain_l1::chains::name::FRONTIER_THAWING => Ok(SpecId::FrontierThawing),
edr_chain_l1::chains::name::HOMESTEAD => Ok(SpecId::Homestead),
edr_chain_l1::chains::name::DAO_FORK => Ok(SpecId::DaoFork),
edr_chain_l1::chains::name::TANGERINE => Ok(SpecId::Tangerine),
edr_chain_l1::chains::name::SPURIOUS_DRAGON => Ok(SpecId::SpuriousDragon),
edr_chain_l1::chains::name::BYZANTIUM => Ok(SpecId::Byzantium),
edr_chain_l1::chains::name::CONSTANTINOPLE => Ok(SpecId::Constantinople),
edr_chain_l1::chains::name::PETERSBURG => Ok(SpecId::Petersburg),
edr_chain_l1::chains::name::ISTANBUL => Ok(SpecId::Istanbul),
edr_chain_l1::chains::name::MUIR_GLACIER => Ok(SpecId::MuirGlacier),
edr_chain_l1::chains::name::BERLIN => Ok(SpecId::Berlin),
edr_chain_l1::chains::name::LONDON => Ok(SpecId::London),
edr_chain_l1::chains::name::ARROW_GLACIER => Ok(SpecId::ArrowGlacier),
edr_chain_l1::chains::name::GRAY_GLACIER => Ok(SpecId::GrayGlacier),
edr_chain_l1::chains::name::MERGE => Ok(SpecId::Merge),
edr_chain_l1::chains::name::SHANGHAI => Ok(SpecId::Shanghai),
edr_chain_l1::chains::name::CANCUN => Ok(SpecId::Cancun),
edr_chain_l1::chains::name::PRAGUE => Ok(SpecId::Prague),
edr_chain_l1::chains::name::OSAKA => Ok(SpecId::Osaka),
_ => Err(napi::Error::new(
napi::Status::InvalidArg,
format!("The provided hardfork `{s}` is not supported."),
)),
}
}
}
impl From<SpecId> for edr_chain_l1::Hardfork {
fn from(value: SpecId) -> Self {
match value {
SpecId::Frontier => edr_chain_l1::Hardfork::FRONTIER,
SpecId::FrontierThawing => edr_chain_l1::Hardfork::FRONTIER_THAWING,
SpecId::Homestead => edr_chain_l1::Hardfork::HOMESTEAD,
SpecId::DaoFork => edr_chain_l1::Hardfork::DAO_FORK,
SpecId::Tangerine => edr_chain_l1::Hardfork::TANGERINE,
SpecId::SpuriousDragon => edr_chain_l1::Hardfork::SPURIOUS_DRAGON,
SpecId::Byzantium => edr_chain_l1::Hardfork::BYZANTIUM,
SpecId::Constantinople => edr_chain_l1::Hardfork::CONSTANTINOPLE,
SpecId::Petersburg => edr_chain_l1::Hardfork::PETERSBURG,
SpecId::Istanbul => edr_chain_l1::Hardfork::ISTANBUL,
SpecId::MuirGlacier => edr_chain_l1::Hardfork::MUIR_GLACIER,
SpecId::Berlin => edr_chain_l1::Hardfork::BERLIN,
SpecId::London => edr_chain_l1::Hardfork::LONDON,
SpecId::ArrowGlacier => edr_chain_l1::Hardfork::ARROW_GLACIER,
SpecId::GrayGlacier => edr_chain_l1::Hardfork::GRAY_GLACIER,
SpecId::Merge => edr_chain_l1::Hardfork::MERGE,
SpecId::Shanghai => edr_chain_l1::Hardfork::SHANGHAI,
SpecId::Cancun => edr_chain_l1::Hardfork::CANCUN,
SpecId::Prague => edr_chain_l1::Hardfork::PRAGUE,
SpecId::Osaka => edr_chain_l1::Hardfork::OSAKA,
}
}
}
/// Tries to parse the provided string to create a [`SpecId`] instance.
///
/// Returns an error if the string does not match any known hardfork.
#[napi(catch_unwind)]
pub fn l1_hardfork_from_string(hardfork: String) -> napi::Result<SpecId> {
hardfork.parse()
}
#[napi(catch_unwind)]
pub fn l1_hardfork_to_string(harfork: SpecId) -> &'static str {
match harfork {
SpecId::Frontier => edr_chain_l1::chains::name::FRONTIER,
SpecId::FrontierThawing => edr_chain_l1::chains::name::FRONTIER_THAWING,
SpecId::Homestead => edr_chain_l1::chains::name::HOMESTEAD,
SpecId::DaoFork => edr_chain_l1::chains::name::DAO_FORK,
SpecId::Tangerine => edr_chain_l1::chains::name::TANGERINE,
SpecId::SpuriousDragon => edr_chain_l1::chains::name::SPURIOUS_DRAGON,
SpecId::Byzantium => edr_chain_l1::chains::name::BYZANTIUM,
SpecId::Constantinople => edr_chain_l1::chains::name::CONSTANTINOPLE,
SpecId::Petersburg => edr_chain_l1::chains::name::PETERSBURG,
SpecId::Istanbul => edr_chain_l1::chains::name::ISTANBUL,
SpecId::MuirGlacier => edr_chain_l1::chains::name::MUIR_GLACIER,
SpecId::Berlin => edr_chain_l1::chains::name::BERLIN,
SpecId::London => edr_chain_l1::chains::name::LONDON,
SpecId::ArrowGlacier => edr_chain_l1::chains::name::ARROW_GLACIER,
SpecId::GrayGlacier => edr_chain_l1::chains::name::GRAY_GLACIER,
SpecId::Merge => edr_chain_l1::chains::name::MERGE,
SpecId::Shanghai => edr_chain_l1::chains::name::SHANGHAI,
SpecId::Cancun => edr_chain_l1::chains::name::CANCUN,
SpecId::Prague => edr_chain_l1::chains::name::PRAGUE,
SpecId::Osaka => edr_chain_l1::chains::name::OSAKA,
}
}
/// Returns the latest supported OP hardfork.
///
/// The returned value will be updated after each network upgrade.
#[napi]
pub fn l1_hardfork_latest() -> SpecId {
SpecId::Osaka
}
macro_rules! export_spec_id {
($($variant:ident),*) => {
$(
#[napi]
pub const $variant: &str = edr_chain_l1::chains::name::$variant;
)*
};
}
export_spec_id!(
FRONTIER,
FRONTIER_THAWING,
HOMESTEAD,
DAO_FORK,
TANGERINE,
SPURIOUS_DRAGON,
BYZANTIUM,
CONSTANTINOPLE,
PETERSBURG,
ISTANBUL,
MUIR_GLACIER,
BERLIN,
LONDON,
ARROW_GLACIER,
GRAY_GLACIER,
MERGE,
SHANGHAI,
CANCUN,
PRAGUE,
OSAKA
);

450
dev/env/node_modules/@nomicfoundation/edr/src/chains/op.rs generated vendored Executable file
View File

@@ -0,0 +1,450 @@
use std::{str::FromStr, sync::Arc};
use edr_napi_core::{
logger::Logger,
provider::{SyncProvider, SyncProviderFactory},
subscription::subscriber_callback_for_chain_spec,
};
use edr_op::{
predeploys::{
gas_price_oracle_code_ecotone, gas_price_oracle_code_fjord, gas_price_oracle_code_isthmus,
l1_block_code_bedrock, l1_block_code_ecotone, l1_block_code_isthmus,
GAS_PRICE_ORACLE_ADDRESS, L1_BLOCK_PREDEPLOY_ADDRESS,
},
OpChainSpec,
};
use edr_primitives::hex;
use edr_provider::time::CurrentTime;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
};
use napi_derive::napi;
use crate::{
account::{AccountOverride, StorageSlot},
provider::ProviderFactory,
};
pub struct OpProviderFactory;
impl SyncProviderFactory for OpProviderFactory {
fn create_provider(
&self,
runtime: runtime::Handle,
provider_config: edr_napi_core::provider::Config,
logger_config: edr_napi_core::logger::Config,
subscription_callback: edr_napi_core::subscription::Callback,
contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<Arc<dyn SyncProvider>> {
let logger =
Logger::<OpChainSpec, CurrentTime>::new(logger_config, Arc::clone(&contract_decoder))?;
let provider_config =
edr_provider::ProviderConfig::<edr_op::Hardfork>::try_from(provider_config)?;
let provider = edr_provider::Provider::<OpChainSpec>::new(
runtime.clone(),
Box::new(logger),
subscriber_callback_for_chain_spec::<OpChainSpec, CurrentTime>(subscription_callback),
provider_config,
contract_decoder,
CurrentTime,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))?;
Ok(Arc::new(provider))
}
}
/// Enumeration of supported OP hardforks.
#[napi]
pub enum OpHardfork {
Bedrock = 100,
Regolith = 101,
Canyon = 102,
Ecotone = 103,
Fjord = 104,
Granite = 105,
Holocene = 106,
Isthmus = 107,
}
impl From<OpHardfork> for edr_op::Hardfork {
fn from(hardfork: OpHardfork) -> Self {
match hardfork {
OpHardfork::Bedrock => edr_op::Hardfork::BEDROCK,
OpHardfork::Regolith => edr_op::Hardfork::REGOLITH,
OpHardfork::Canyon => edr_op::Hardfork::CANYON,
OpHardfork::Ecotone => edr_op::Hardfork::ECOTONE,
OpHardfork::Fjord => edr_op::Hardfork::FJORD,
OpHardfork::Granite => edr_op::Hardfork::GRANITE,
OpHardfork::Holocene => edr_op::Hardfork::HOLOCENE,
OpHardfork::Isthmus => edr_op::Hardfork::ISTHMUS,
}
}
}
impl FromStr for OpHardfork {
type Err = napi::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
edr_op::hardfork::name::BEDROCK => Ok(OpHardfork::Bedrock),
edr_op::hardfork::name::REGOLITH => Ok(OpHardfork::Regolith),
edr_op::hardfork::name::CANYON => Ok(OpHardfork::Canyon),
edr_op::hardfork::name::ECOTONE => Ok(OpHardfork::Ecotone),
edr_op::hardfork::name::FJORD => Ok(OpHardfork::Fjord),
edr_op::hardfork::name::GRANITE => Ok(OpHardfork::Granite),
edr_op::hardfork::name::HOLOCENE => Ok(OpHardfork::Holocene),
edr_op::hardfork::name::ISTHMUS => Ok(OpHardfork::Isthmus),
_ => Err(napi::Error::new(
napi::Status::InvalidArg,
format!("The provided OP hardfork `{s}` is not supported."),
)),
}
}
}
/// Tries to parse the provided string to create an [`OpHardfork`]
/// instance.
///
/// Returns an error if the string does not match any known hardfork.
#[napi(catch_unwind)]
pub fn op_hardfork_from_string(hardfork: String) -> napi::Result<OpHardfork> {
hardfork.parse()
}
/// Returns the string representation of the provided OP hardfork.
#[napi(catch_unwind)]
pub fn op_hardfork_to_string(hardfork: OpHardfork) -> &'static str {
match hardfork {
OpHardfork::Bedrock => edr_op::hardfork::name::BEDROCK,
OpHardfork::Regolith => edr_op::hardfork::name::REGOLITH,
OpHardfork::Canyon => edr_op::hardfork::name::CANYON,
OpHardfork::Ecotone => edr_op::hardfork::name::ECOTONE,
OpHardfork::Fjord => edr_op::hardfork::name::FJORD,
OpHardfork::Granite => edr_op::hardfork::name::GRANITE,
OpHardfork::Holocene => edr_op::hardfork::name::HOLOCENE,
OpHardfork::Isthmus => edr_op::hardfork::name::ISTHMUS,
}
}
/// Returns the latest supported OP hardfork.
///
/// The returned value will be updated after each network upgrade.
#[napi(catch_unwind)]
pub fn op_latest_hardfork() -> OpHardfork {
OpHardfork::Isthmus
}
#[napi]
pub const OP_CHAIN_TYPE: &str = edr_op::CHAIN_TYPE;
#[napi(catch_unwind)]
pub fn op_genesis_state(hardfork: OpHardfork) -> Vec<AccountOverride> {
let l1_block_code = l1_block_code(hardfork.into());
let l1_block = AccountOverride {
address: Uint8Array::with_data_copied(L1_BLOCK_PREDEPLOY_ADDRESS),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(l1_block_code),
storage: Some(l1_block_storage(hardfork.into())),
};
/* The rest of the predeploys use a stubbed bytecode that reverts with a
message indicating that the predeploy is not supported. For each of
them, the Solidity code that generates the bytecode is:
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract NotSupported {
fallback() external payable {
revert("Predeploy <PredeployName> is not supported.");
}
}
*/
let stubbed_predeploys_data = vec![
(
"LegacyMessagePasser",
hex!("4200000000000000000000000000000000000000"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602f8360bf565b91507f5072656465706c6f79204c65676163794d65737361676550617373657220697360008301527f206e6f7420737570706f727465642e00000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212206ba272e31c33ce6fe2612b534c5aa5ed8905e1bed8a757ff1a74cc06509a17f664736f6c63430008000033",
),
(
"DeployerWhitelist",
hex!("4200000000000000000000000000000000000002"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602d8360bf565b91507f5072656465706c6f79204465706c6f79657257686974656c697374206973206e60008301527f6f7420737570706f727465642e000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212206af5fc0549e5db963a08cb2864cbbf5c4e27efb08219fc0e29bda83f84b121ac64736f6c63430008000033",
),
(
"LegacyERC20ETH",
hex!("DeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f79204c65676163794552433230455448206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122054e7f9d6c12400d5b4b67aed39be8c44a8b1461519e96a0e7764c69417239c7964736f6c63430008000033",
),
(
"WETH9",
hex!("4200000000000000000000000000000000000006"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860218360bf565b91507f5072656465706c6f79205745544839206973206e6f7420737570706f7274656460008301527f2e000000000000000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220860ec43d585e1b040780713555b6fc492d748c73586bdb8f2b9af441c4452dbf64736f6c63430008000033",
),
(
"L2CrossDomainMessenger",
hex!("4200000000000000000000000000000000000007"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860328360bf565b91507f5072656465706c6f79204c3243726f7373446f6d61696e4d657373656e67657260008301527f206973206e6f7420737570706f727465642e00000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212200fadec69889de49a1a3a14d4e7e477e00921681e12650f510863d0077c16f58864736f6c63430008000033",
),
(
"L2StandardBridge",
hex!("4200000000000000000000000000000000000010"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602c8360bf565b91507f5072656465706c6f79204c325374616e64617264427269646765206973206e6f60008301527f7420737570706f727465642e00000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220ce5c24ee894b04d974b95cd204ab35f85906430ba6f49d1ea70d3d0c9c204cb764736f6c63430008000033",
),
(
"SequencerFeeVault",
hex!("4200000000000000000000000000000000000011"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602d8360bf565b91507f5072656465706c6f792053657175656e6365724665655661756c74206973206e60008301527f6f7420737570706f727465642e000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212203990ed752a94bb02bd5162fef116c1b62079e8207c5164b3ae5a115f5cf0b31164736f6c63430008000033",
),
(
"OptimismMintableERC20Factory",
hex!("4200000000000000000000000000000000000012"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860388360bf565b91507f5072656465706c6f79204f7074696d69736d4d696e7461626c6545524332304660008301527f6163746f7279206973206e6f7420737570706f727465642e00000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220240605543d69b93641a24f1d153969c3969089a04a162fc9f18f95de926b385564736f6c63430008000033",
),
(
"L1BlockNumber",
hex!("4200000000000000000000000000000000000013"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860298360bf565b91507f5072656465706c6f79204c31426c6f636b4e756d626572206973206e6f74207360008301527f7570706f727465642e00000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122099ba6da366313d162bab19a497fab2200808ddd24935b9f8be496c3622110b1164736f6c63430008000033",
),
(
"GovernanceToken",
hex!("4200000000000000000000000000000000000042"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602b8360bf565b91507f5072656465706c6f7920476f7665726e616e6365546f6b656e206973206e6f7460008301527f20737570706f727465642e0000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212205a22322e97c15d3a28eb86abac215ed31bcf6e0cf562e2679ce5fb3495953cfc64736f6c63430008000033",
),
(
"L2ToL1MessagePasser",
hex!("4200000000000000000000000000000000000016"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602f8360bf565b91507f5072656465706c6f79204c32546f4c314d65737361676550617373657220697360008301527f206e6f7420737570706f727465642e00000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212205b2ed2ecc932d0e4a45e97ae7ac256e58848453ac06733b27890587962871a1864736f6c63430008000033",
),
(
"L2ERC721Bridge",
hex!("4200000000000000000000000000000000000014"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f79204c32455243373231427269646765206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea26469706673582212203f9de306b34383b29e9dfb174fd424d7e11d31e8859d0e96a2aa3a46609e826c64736f6c63430008000033",
),
(
"OptimismMintableERC721Factory",
hex!("4200000000000000000000000000000000000017"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860398360bf565b91507f5072656465706c6f79204f7074696d69736d4d696e7461626c6545524337323160008301527f466163746f7279206973206e6f7420737570706f727465642e000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea264697066735822122033131ae0c34f3246f5031971388431bd1dfb1b92d6b08d92a0a905911c1eeeeb64736f6c63430008000033",
),
(
"ProxyAdmin",
hex!("4200000000000000000000000000000000000018"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860268360bf565b91507f5072656465706c6f792050726f787941646d696e206973206e6f74207375707060008301527f6f727465642e00000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220c7b191ff1b21c73fb6a26fd1e972d6844631a700b7a316ca2d9e04905af44dbb64736f6c63430008000033",
),
(
"BaseFeeVault",
hex!("4200000000000000000000000000000000000019"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860288360bf565b91507f5072656465706c6f7920426173654665655661756c74206973206e6f7420737560008301527f70706f727465642e0000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220535ae2b8a6393c0be4de1dce095f5e17fc0c7a46b40ac7793db894328f1799e764736f6c63430008000033",
),
(
"L1FeeVault",
hex!("420000000000000000000000000000000000001a"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b6000604860268360bf565b91507f5072656465706c6f79204c314665655661756c74206973206e6f74207375707060008301527f6f727465642e00000000000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220dac6ab093c79da782b6e98ec67c48758f6c1cb80cba58e080c114a9b8c93befc64736f6c63430008000033",
),
(
"SchemaRegistry",
hex!("4200000000000000000000000000000000000020"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060a1565b60405180910390fd5b60006048602a8360bf565b91507f5072656465706c6f7920536368656d615265676973747279206973206e6f742060008301527f737570706f727465642e000000000000000000000000000000000000000000006020830152604082019050919050565b6000602082019050818103600083015260b881603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220b3daf5355920b581943cabb92a7cc67123467fdd1b054cb0c5f0e587c08da1be64736f6c63430008000033",
),
(
"EAS",
hex!("4200000000000000000000000000000000000021"),
"0x60806040526040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603490607b565b60405180910390fd5b60006048601f836099565b91507f5072656465706c6f7920454153206973206e6f7420737570706f727465642e006000830152602082019050919050565b60006020820190508181036000830152609281603d565b9050919050565b60008282526020820190509291505056fea2646970667358221220afa6c1aa54a8b3f4f979e1297db5838a94353f3b77b5ecc164da19db26ea89f564736f6c63430008000033",
),
(
"OperatorFeeVault",
hex!("0x420000000000000000000000000000000000001b"),
"0x60806040526040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160349060b9565b60405180910390fd5b5f82825260208201905092915050565b7f5072656465706c6f79204f70657261746f724665655661756c74206973206e6f5f8201527f7420737570706f727465642e0000000000000000000000000000000000000000602082015250565b5f60a5602c83603d565b915060ae82604d565b604082019050919050565b5f6020820190508181035f83015260ce81609b565b905091905056fea2646970667358221220dc3131d0ea77326c36012aee5dd9a870b6f07d76e6f55c8029da9d70a83f50c364736f6c634300081e0033",
),
];
let stubbed_predeploys = stubbed_predeploys_data
.iter()
.map(|(name, address, code)| AccountOverride {
address: address.into(),
balance: Some(BigInt::from(0u64)),
nonce: Some(BigInt::from(0u64)),
code: Some(
hex::decode(code)
.unwrap_or_else(|e| panic!("The bytecode for the {name} predeploy should be a valid hex string, got error: {e}"))
.into(),
),
storage: Some(vec![]),
});
let predeploys = vec![gas_price_oracle_override(hardfork.into()), l1_block];
predeploys.into_iter().chain(stubbed_predeploys).collect()
}
#[napi(catch_unwind)]
pub fn op_provider_factory() -> ProviderFactory {
let factory: Arc<dyn SyncProviderFactory> = Arc::new(OpProviderFactory);
factory.into()
}
fn gas_price_oracle_override(hardfork: edr_op::Hardfork) -> AccountOverride {
if hardfork >= edr_op::Hardfork::ISTHMUS {
gas_price_oracle_isthmus()
} else if hardfork >= edr_op::Hardfork::FJORD {
gas_price_oracle_fjord()
} else {
gas_price_oracle_ecotone()
}
}
fn gas_price_oracle_ecotone() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_ecotone().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000000001u64,
),
}]),
}
}
fn gas_price_oracle_fjord() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_fjord().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
// bool isFjord = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000000101u64,
),
}]),
}
}
fn gas_price_oracle_isthmus() -> AccountOverride {
AccountOverride {
address: Uint8Array::with_data_copied(GAS_PRICE_ORACLE_ADDRESS),
balance: None,
nonce: None,
code: Some(gas_price_oracle_code_isthmus().into()),
storage: Some(vec![StorageSlot {
index: BigInt::from(0u64),
// bool isEcotone = true
// bool isFjord = true
// bool isIsthmus = true
value: BigInt::from(
0x0000000000000000000000000000000000000000000000000000000000010101u64,
),
}]),
}
}
fn l1_block_storage(hardfork: edr_op::Hardfork) -> Vec<StorageSlot> {
let mut base_storage = vec![
StorageSlot {
index: BigInt::from(0u64),
// uint64 public number = 1
// uint64 public timestamp = 1
value: BigInt {
words: vec![
0x0000000000000001_u64, // least significative
0x0000000000000001_u64,
],
sign_bit: false,
},
},
StorageSlot {
index: BigInt::from(1u64),
// uint256 baseFee = 10 gwei
value: BigInt::from(0x00000002540be400_u64),
},
StorageSlot {
index: BigInt::from(2u64),
// bytes32 hash = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(3u64),
// uint64 sequenceNumber = 0
// uint32 blobBaseFeeScalar = 1014213
// uint32 baseFeeScalar = 5227
value: BigInt {
words: vec![
0x0000000000000000_u64, // least significative
0x0000000000000000_u64,
0x00000000000f79c5_u64,
0x000000000000146b_u64,
],
sign_bit: false,
},
},
StorageSlot {
index: BigInt::from(4u64),
// bytes32 batcherHash = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(5u64),
// uint256 l1FeeOverhead = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(6u64),
// uint256 l1FeeScalar = 0
value: BigInt::from(0u64),
},
StorageSlot {
index: BigInt::from(7u64),
// uint256 blobBaseFee = 10 gwei
value: BigInt::from(0x00000002540be400_u64),
},
];
if hardfork >= edr_op::Hardfork::ISTHMUS {
base_storage.push(StorageSlot {
// Operator fee parameters
index: BigInt::from(8u64),
value: BigInt::from(0u64),
});
}
base_storage
}
fn l1_block_code(hardfork: edr_op::Hardfork) -> Uint8Array {
if hardfork >= edr_op::Hardfork::ISTHMUS {
l1_block_code_isthmus().into()
} else if hardfork >= edr_op::Hardfork::ECOTONE {
l1_block_code_ecotone().into()
} else {
l1_block_code_bedrock().into()
}
}
macro_rules! export_spec_id {
($($variant:ident,)*) => {
$(
#[napi]
pub const $variant: &str = edr_op::hardfork::name::$variant;
)*
};
}
export_spec_id! {
BEDROCK,
REGOLITH,
CANYON,
ECOTONE,
FJORD,
GRANITE,
HOLOCENE,
ISTHMUS,
}

710
dev/env/node_modules/@nomicfoundation/edr/src/config.rs generated vendored Executable file
View File

@@ -0,0 +1,710 @@
use core::fmt::{Debug, Display};
use std::{
num::NonZeroU64,
path::PathBuf,
time::{Duration, SystemTime},
};
use edr_coverage::reporter::SyncOnCollectedCoverageCallback;
use edr_eip1559::{BaseFeeActivation, ConstantBaseFeeParams};
use edr_gas_report::SyncOnCollectedGasReportCallback;
use edr_primitives::{Bytes, HashMap, HashSet};
use edr_signer::{secret_key_from_str, SecretKey};
use napi::{
bindgen_prelude::{BigInt, Promise, Reference, Uint8Array},
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::runtime,
Either, JsFunction, JsString, JsStringUtf8,
};
use napi_derive::napi;
use crate::{
account::AccountOverride, block::BlobGas, cast::TryCast, gas_report::GasReport,
logger::LoggerConfig, precompile::Precompile, subscription::SubscriptionConfig,
};
/// Configuration for EIP-1559 parameters
#[napi(object)]
pub struct BaseFeeParamActivation {
pub activation: Either<BaseFeeActivationByBlockNumber, BaseFeeActivationByHardfork>,
pub max_change_denominator: BigInt,
pub elasticity_multiplier: BigInt,
}
#[napi(object)]
pub struct BaseFeeActivationByBlockNumber {
/// The block number at which the `base_fee_params` is activated
pub block_number: BigInt,
}
#[napi(object)]
pub struct BaseFeeActivationByHardfork {
/// The hardfork at which the `base_fee_params` is activated
pub hardfork: String,
}
impl TryFrom<BaseFeeParamActivation> for (BaseFeeActivation<String>, ConstantBaseFeeParams) {
type Error = napi::Error;
fn try_from(value: BaseFeeParamActivation) -> Result<Self, Self::Error> {
let base_fee_params = ConstantBaseFeeParams {
max_change_denominator: value.max_change_denominator.try_cast()?,
elasticity_multiplier: value.elasticity_multiplier.try_cast()?,
};
match value.activation {
Either::A(BaseFeeActivationByBlockNumber { block_number }) => {
let activation_block_number: u64 = block_number.try_cast()?;
Ok((
BaseFeeActivation::BlockNumber(activation_block_number),
base_fee_params,
))
}
Either::B(BaseFeeActivationByHardfork { hardfork }) => {
Ok((BaseFeeActivation::Hardfork(hardfork), base_fee_params))
}
}
}
}
/// Specification of a chain with possible overrides.
#[napi(object)]
pub struct ChainOverride {
/// The chain ID
pub chain_id: BigInt,
/// The chain's name
pub name: String,
/// If present, overrides for the chain's supported hardforks
pub hardfork_activation_overrides: Option<Vec<HardforkActivation>>,
}
/// Configuration for a code coverage reporter.
#[napi(object)]
pub struct CodeCoverageConfig {
/// The callback to be called when coverage has been collected.
///
/// The callback receives an array of unique coverage hit markers (i.e. no
/// repetition) per transaction.
///
/// Exceptions thrown in the callback will be propagated to the original
/// caller.
#[napi(ts_type = "(coverageHits: Uint8Array[]) => Promise<void>")]
pub on_collected_coverage_callback: JsFunction,
}
#[napi(object)]
pub struct GasReportConfig {
/// Gas reports are collected after a block is mined or `eth_call` is
/// executed.
///
/// Exceptions thrown in the callback will be propagated to the original
/// caller.
#[napi(ts_type = "(gasReport: GasReport) => Promise<void>")]
pub on_collected_gas_report_callback: JsFunction,
}
/// Configuration for forking a blockchain
#[napi(object)]
pub struct ForkConfig {
/// The block number to fork from. If not provided, the latest safe block is
/// used.
pub block_number: Option<BigInt>,
/// The directory to cache remote JSON-RPC responses
pub cache_dir: Option<String>,
/// Overrides for the configuration of chains.
pub chain_overrides: Option<Vec<ChainOverride>>,
/// The HTTP headers to use when making requests to the JSON-RPC endpoint
pub http_headers: Option<Vec<HttpHeader>>,
/// The URL of the JSON-RPC endpoint to fork from
pub url: String,
}
#[napi(object)]
pub struct HttpHeader {
pub name: String,
pub value: String,
}
/// Configuration for a hardfork activation
#[napi(object)]
pub struct HardforkActivation {
/// The condition for the hardfork activation
pub condition: Either<HardforkActivationByBlockNumber, HardforkActivationByTimestamp>,
/// The activated hardfork
pub hardfork: String,
}
#[napi(object)]
pub struct HardforkActivationByBlockNumber {
/// The block number at which the hardfork is activated
pub block_number: BigInt,
}
#[napi(object)]
pub struct HardforkActivationByTimestamp {
/// The timestamp at which the hardfork is activated
pub timestamp: BigInt,
}
#[napi(string_enum)]
#[doc = "The type of ordering to use when selecting blocks to mine."]
pub enum MineOrdering {
#[doc = "Insertion order"]
Fifo,
#[doc = "Effective miner fee"]
Priority,
}
/// Configuration for the provider's mempool.
#[napi(object)]
pub struct MemPoolConfig {
pub order: MineOrdering,
}
#[napi(object)]
pub struct IntervalRange {
pub min: BigInt,
pub max: BigInt,
}
/// Configuration for the provider's miner.
#[napi(object)]
pub struct MiningConfig {
pub auto_mine: bool,
pub interval: Option<Either<BigInt, IntervalRange>>,
pub mem_pool: MemPoolConfig,
}
/// Configuration for runtime observability.
#[napi(object)]
pub struct ObservabilityConfig {
/// If present, configures runtime observability to collect code coverage.
pub code_coverage: Option<CodeCoverageConfig>,
/// If present, configures runtime observability to collect gas reports.
pub gas_report: Option<GasReportConfig>,
}
/// Configuration for a provider
#[napi(object)]
pub struct ProviderConfig {
/// Whether to allow blocks with the same timestamp
pub allow_blocks_with_same_timestamp: bool,
/// Whether to allow unlimited contract size
pub allow_unlimited_contract_size: bool,
/// Whether to return an `Err` when `eth_call` fails
pub bail_on_call_failure: bool,
/// Whether to return an `Err` when a `eth_sendTransaction` fails
pub bail_on_transaction_failure: bool,
/// EIP-1559 base fee parameters activations to be used to calculate the
/// block base fee.
///
/// Provide an ordered list of `base_fee_params` to be
/// used starting from the specified activation point (hardfork or block
/// number).
/// If not provided, the default values from the chain spec
/// will be used.
pub base_fee_config: Option<Vec<BaseFeeParamActivation>>,
/// The gas limit of each block
pub block_gas_limit: BigInt,
/// The chain ID of the blockchain
pub chain_id: BigInt,
/// The address of the coinbase
pub coinbase: Uint8Array,
/// The configuration for forking a blockchain. If not provided, a local
/// blockchain will be created
pub fork: Option<ForkConfig>,
/// The genesis state of the blockchain
pub genesis_state: Vec<AccountOverride>,
/// The hardfork of the blockchain
pub hardfork: String,
/// The initial base fee per gas of the blockchain. Required for EIP-1559
/// transactions and later
pub initial_base_fee_per_gas: Option<BigInt>,
/// The initial blob gas of the blockchain. Required for EIP-4844
pub initial_blob_gas: Option<BlobGas>,
/// The initial date of the blockchain, in seconds since the Unix epoch
pub initial_date: Option<BigInt>,
/// The initial parent beacon block root of the blockchain. Required for
/// EIP-4788
pub initial_parent_beacon_block_root: Option<Uint8Array>,
/// The minimum gas price of the next block.
pub min_gas_price: BigInt,
/// The configuration for the miner
pub mining: MiningConfig,
/// The network ID of the blockchain
pub network_id: BigInt,
/// The configuration for the provider's observability
pub observability: ObservabilityConfig,
// Using JsString here as it doesn't have `Debug`, `Display` and `Serialize` implementation
// which prevents accidentally leaking the secret keys to error messages and logs.
/// Secret keys of owned accounts
pub owned_accounts: Vec<JsString>,
/// Overrides for precompiles
pub precompile_overrides: Vec<Reference<Precompile>>,
/// Transaction gas cap, introduced in [EIP-7825].
///
/// When not set, will default to value defined by the used hardfork
///
/// [EIP-7825]: https://eips.ethereum.org/EIPS/eip-7825
pub transaction_gas_cap: Option<BigInt>,
}
impl TryFrom<ForkConfig> for edr_provider::ForkConfig<String> {
type Error = napi::Error;
fn try_from(value: ForkConfig) -> Result<Self, Self::Error> {
let block_number: Option<u64> = value.block_number.map(TryCast::try_cast).transpose()?;
let cache_dir = PathBuf::from(
value
.cache_dir
.unwrap_or(edr_defaults::CACHE_DIR.to_owned()),
);
let chain_overrides = value
.chain_overrides
.map(|chain_overrides| {
chain_overrides
.into_iter()
.map(
|ChainOverride {
chain_id,
name,
hardfork_activation_overrides,
}| {
let hardfork_activation_overrides =
hardfork_activation_overrides
.map(|hardfork_activations| {
hardfork_activations
.into_iter()
.map(
|HardforkActivation {
condition,
hardfork,
}| {
let condition = match condition {
Either::A(HardforkActivationByBlockNumber {
block_number,
}) => edr_chain_config::ForkCondition::Block(
block_number.try_cast()?,
),
Either::B(HardforkActivationByTimestamp {
timestamp,
}) => edr_chain_config::ForkCondition::Timestamp(
timestamp.try_cast()?,
),
};
Ok(edr_chain_config::HardforkActivation {
condition,
hardfork,
})
},
)
.collect::<napi::Result<Vec<_>>>()
.map(edr_chain_config::HardforkActivations::new)
})
.transpose()?;
let chain_config = edr_chain_config::ChainOverride {
name,
hardfork_activation_overrides,
};
let chain_id = chain_id.try_cast()?;
Ok((chain_id, chain_config))
},
)
.collect::<napi::Result<_>>()
})
.transpose()?;
let http_headers = value.http_headers.map(|http_headers| {
http_headers
.into_iter()
.map(|HttpHeader { name, value }| (name, value))
.collect()
});
Ok(Self {
block_number,
cache_dir,
chain_overrides: chain_overrides.unwrap_or_default(),
http_headers,
url: value.url,
})
}
}
impl From<MemPoolConfig> for edr_provider::MemPoolConfig {
fn from(value: MemPoolConfig) -> Self {
Self {
order: value.order.into(),
}
}
}
impl From<MineOrdering> for edr_block_miner::MineOrdering {
fn from(value: MineOrdering) -> Self {
match value {
MineOrdering::Fifo => Self::Fifo,
MineOrdering::Priority => Self::Priority,
}
}
}
impl TryFrom<MiningConfig> for edr_provider::MiningConfig {
type Error = napi::Error;
fn try_from(value: MiningConfig) -> Result<Self, Self::Error> {
let mem_pool = value.mem_pool.into();
let interval = value
.interval
.map(|interval| {
let interval = match interval {
Either::A(interval) => {
let interval = interval.try_cast()?;
let interval = NonZeroU64::new(interval).ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
"Interval must be greater than 0",
)
})?;
edr_provider::IntervalConfig::Fixed(interval)
}
Either::B(IntervalRange { min, max }) => edr_provider::IntervalConfig::Range {
min: min.try_cast()?,
max: max.try_cast()?,
},
};
napi::Result::Ok(interval)
})
.transpose()?;
Ok(Self {
auto_mine: value.auto_mine,
interval,
mem_pool,
})
}
}
impl ObservabilityConfig {
/// Resolves the instance, converting it to a
/// [`edr_provider::observability::Config`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_provider::observability::Config> {
let on_collected_coverage_fn = self
.code_coverage
.map(
|code_coverage| -> napi::Result<Box<dyn SyncOnCollectedCoverageCallback>> {
let runtime = runtime.clone();
let mut on_collected_coverage_callback: ThreadsafeFunction<
_,
ErrorStrategy::Fatal,
> = code_coverage
.on_collected_coverage_callback
.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<HashSet<Bytes>>| {
let hits = ctx
.env
.create_array_with_length(ctx.value.len())
.and_then(|mut hits| {
for (idx, hit) in ctx.value.into_iter().enumerate() {
ctx.env
.create_buffer_with_data(hit.to_vec())
.and_then(|hit| {
let idx = u32::try_from(idx).unwrap_or_else(|_| panic!("Number of hits should not exceed '{}'",
u32::MAX));
hits.set_element(idx, hit.into_raw())
})?;
}
Ok(hits)
})?;
Ok(vec![hits])
},
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
on_collected_coverage_callback.unref(env)?;
let on_collected_coverage_fn: Box<dyn SyncOnCollectedCoverageCallback> =
Box::new(move |hits| {
let runtime = runtime.clone();
let (sender, receiver) = std::sync::mpsc::channel();
let status = on_collected_coverage_callback
.call_with_return_value(hits, ThreadsafeFunctionCallMode::Blocking, move |result: Promise<()>| {
// We spawn a background task to handle the async callback
runtime.spawn(async move {
let result = result.await;
sender.send(result).map_err(|_error| {
napi::Error::new(
napi::Status::GenericFailure,
"Failed to send result from on_collected_coverage_callback",
)
})
});
Ok(())
});
assert_eq!(status, napi::Status::Ok);
let () = receiver.recv().expect("Receive can only fail if the channel is closed")?;
Ok(())
});
Ok(on_collected_coverage_fn)
},
)
.transpose()?;
let on_collected_gas_report_fn = self.gas_report.map(
|gas_report| -> napi::Result<Box<dyn SyncOnCollectedGasReportCallback>> {
let mut on_collected_gas_report_callback: ThreadsafeFunction<
_,
ErrorStrategy::Fatal,
> = gas_report
.on_collected_gas_report_callback
.create_threadsafe_function(
0,
|ctx: ThreadSafeCallContext<GasReport>| {
let report = ctx.value;
Ok(vec![report])
}
,
)?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
on_collected_gas_report_callback.unref(env)?;
let on_collected_gas_report_fn: Box<dyn SyncOnCollectedGasReportCallback> =
Box::new(move |report| {
let runtime = runtime.clone();
let (sender, receiver) = std::sync::mpsc::channel();
// Convert the report to the N-API representation
let status = on_collected_gas_report_callback
.call_with_return_value(GasReport::from(report), ThreadsafeFunctionCallMode::Blocking, move |result: Promise<()>| {
// We spawn a background task to handle the async callback
runtime.spawn(async move {
let result = result.await;
sender.send(result).map_err(|_error| {
napi::Error::new(
napi::Status::GenericFailure,
"Failed to send result from on_collected_gas_report_callback",
)
})
});
Ok(())
});
assert_eq!(status, napi::Status::Ok);
let () = receiver.recv().expect("Receive can only fail if the channel is closed")?;
Ok(())
});
Ok(on_collected_gas_report_fn)
},
).transpose()?;
Ok(edr_provider::observability::Config {
on_collected_coverage_fn,
on_collected_gas_report_fn,
..edr_provider::observability::Config::default()
})
}
}
impl ProviderConfig {
/// Resolves the instance to a [`edr_napi_core::provider::Config`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_napi_core::provider::Config> {
let owned_accounts = self
.owned_accounts
.into_iter()
.map(|secret_key| {
// This is the only place in production code where it's allowed to use
// `DangerousSecretKeyStr`.
#[allow(deprecated)]
use edr_signer::DangerousSecretKeyStr;
static_assertions::assert_not_impl_all!(JsString: Debug, Display, serde::Serialize);
static_assertions::assert_not_impl_all!(JsStringUtf8: Debug, Display, serde::Serialize);
// `SecretKey` has `Debug` implementation, but it's opaque (only shows the
// type name)
static_assertions::assert_not_impl_any!(SecretKey: Display, serde::Serialize);
let secret_key = secret_key.into_utf8()?;
// This is the only place in production code where it's allowed to use
// `DangerousSecretKeyStr`.
#[allow(deprecated)]
let secret_key_str = DangerousSecretKeyStr(secret_key.as_str()?);
let secret_key: SecretKey = secret_key_from_str(secret_key_str)
.map_err(|error| napi::Error::new(napi::Status::InvalidArg, error))?;
Ok(secret_key)
})
.collect::<napi::Result<Vec<_>>>()?;
let base_fee_params: Option<Vec<(BaseFeeActivation<String>, ConstantBaseFeeParams)>> = self
.base_fee_config
.map(|vec| vec.into_iter().map(TryInto::try_into).collect())
.transpose()?;
let block_gas_limit =
NonZeroU64::new(self.block_gas_limit.try_cast()?).ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
"Block gas limit must be greater than 0",
)
})?;
let genesis_state = self
.genesis_state
.into_iter()
.map(TryInto::try_into)
.collect::<napi::Result<HashMap<edr_primitives::Address, edr_provider::AccountOverride>>>()?;
let precompile_overrides = self
.precompile_overrides
.into_iter()
.map(|precompile| precompile.to_tuple())
.collect();
Ok(edr_napi_core::provider::Config {
allow_blocks_with_same_timestamp: self.allow_blocks_with_same_timestamp,
allow_unlimited_contract_size: self.allow_unlimited_contract_size,
bail_on_call_failure: self.bail_on_call_failure,
bail_on_transaction_failure: self.bail_on_transaction_failure,
base_fee_params,
block_gas_limit,
chain_id: self.chain_id.try_cast()?,
coinbase: self.coinbase.try_cast()?,
fork: self.fork.map(TryInto::try_into).transpose()?,
genesis_state,
hardfork: self.hardfork,
initial_base_fee_per_gas: self
.initial_base_fee_per_gas
.map(TryCast::try_cast)
.transpose()?,
initial_blob_gas: self.initial_blob_gas.map(TryInto::try_into).transpose()?,
initial_date: self
.initial_date
.map(|date| {
let elapsed_since_epoch = Duration::from_secs(date.try_cast()?);
napi::Result::Ok(SystemTime::UNIX_EPOCH + elapsed_since_epoch)
})
.transpose()?,
initial_parent_beacon_block_root: self
.initial_parent_beacon_block_root
.map(TryCast::try_cast)
.transpose()?,
mining: self.mining.try_into()?,
min_gas_price: self.min_gas_price.try_cast()?,
network_id: self.network_id.try_cast()?,
observability: self.observability.resolve(env, runtime)?,
owned_accounts,
precompile_overrides,
transaction_gas_cap: self
.transaction_gas_cap
.map(TryCast::try_cast)
.transpose()?,
})
}
}
/// Tracing config for Solidity stack trace generation.
#[napi(object)]
pub struct TracingConfigWithBuffers {
/// Build information to use for decoding contracts. Either a Hardhat v2
/// build info file that contains both input and output or a Hardhat v3
/// build info file that doesn't contain output and a separate output file.
pub build_infos: Option<Either<Vec<Uint8Array>, Vec<BuildInfoAndOutput>>>,
/// Whether to ignore contracts whose name starts with "Ignored".
pub ignore_contracts: Option<bool>,
}
impl From<TracingConfigWithBuffers> for edr_napi_core::solidity::config::TracingConfigWithBuffers {
fn from(value: TracingConfigWithBuffers) -> Self {
edr_napi_core::solidity::config::TracingConfigWithBuffers {
build_infos: value.build_infos.map(|infos| match infos {
Either::A(with_output) => Either::A(with_output),
Either::B(separate_output) => Either::B(
separate_output
.into_iter()
.map(edr_napi_core::solidity::config::BuildInfoAndOutput::from)
.collect(),
),
}),
ignore_contracts: value.ignore_contracts,
}
}
}
/// Hardhat V3 build info where the compiler output is not part of the build
/// info file.
#[napi(object)]
pub struct BuildInfoAndOutput {
/// The build info input file
pub build_info: Uint8Array,
/// The build info output file
pub output: Uint8Array,
}
impl From<BuildInfoAndOutput> for edr_napi_core::solidity::config::BuildInfoAndOutput {
fn from(value: BuildInfoAndOutput) -> Self {
Self {
build_info: value.build_info,
output: value.output,
}
}
}
/// Result of [`resolve_configs`].
pub struct ConfigResolution {
pub logger_config: edr_napi_core::logger::Config,
pub provider_config: edr_napi_core::provider::Config,
pub subscription_callback: edr_napi_core::subscription::Callback,
}
/// Helper function for resolving the provided N-API configs.
pub fn resolve_configs(
env: &napi::Env,
runtime: runtime::Handle,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
) -> napi::Result<ConfigResolution> {
let provider_config = provider_config.resolve(env, runtime)?;
let logger_config = logger_config.resolve(env)?;
let subscription_config = edr_napi_core::subscription::Config::from(subscription_config);
let subscription_callback =
edr_napi_core::subscription::Callback::new(env, subscription_config.subscription_callback)?;
Ok(ConfigResolution {
logger_config,
provider_config,
subscription_callback,
})
}

447
dev/env/node_modules/@nomicfoundation/edr/src/context.rs generated vendored Executable file
View File

@@ -0,0 +1,447 @@
use std::sync::Arc;
use edr_napi_core::{provider::SyncProviderFactory, solidity};
use edr_primitives::HashMap;
use edr_solidity_tests::{
decode::RevertDecoder,
multi_runner::{SuiteResultAndArtifactId, TestContract, TestContracts},
TestFilterConfig,
};
use napi::{
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
tokio::{runtime, sync::Mutex as AsyncMutex},
Env, JsFunction, JsObject,
};
use napi_derive::napi;
use tracing_subscriber::{prelude::*, EnvFilter, Registry};
use crate::{
config::{resolve_configs, ConfigResolution, ProviderConfig, TracingConfigWithBuffers},
contract_decoder::ContractDecoder,
logger::LoggerConfig,
provider::{Provider, ProviderFactory},
solidity_tests::{
artifact::{Artifact, ArtifactId},
config::SolidityTestRunnerConfigArgs,
factory::SolidityTestRunnerFactory,
test_results::{SolidityTestResult, SuiteResult},
LinkingOutput,
},
subscription::SubscriptionConfig,
};
#[napi]
pub struct EdrContext {
inner: Arc<AsyncMutex<Context>>,
}
#[napi]
impl EdrContext {
/// Creates a new [`EdrContext`] instance. Should only be called once!
#[napi(catch_unwind, constructor)]
pub fn new() -> napi::Result<Self> {
let context = Context::new()?;
Ok(Self {
inner: Arc::new(AsyncMutex::new(context)),
})
}
/// Constructs a new provider with the provided configuration.
#[napi(catch_unwind, ts_return_type = "Promise<Provider>")]
pub fn create_provider(
&self,
env: Env,
chain_type: String,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
contract_decoder: &ContractDecoder,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
macro_rules! try_or_reject_promise {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
}
};
}
let runtime = runtime::Handle::current();
let ConfigResolution {
logger_config,
provider_config,
subscription_callback,
} = try_or_reject_promise!(resolve_configs(
&env,
runtime.clone(),
provider_config,
logger_config,
subscription_config,
));
#[cfg(feature = "scenarios")]
let scenario_file =
try_or_reject_promise!(runtime.clone().block_on(crate::scenarios::scenario_file(
chain_type.clone(),
provider_config.clone(),
logger_config.enable,
)));
let factory = {
// TODO: https://github.com/NomicFoundation/edr/issues/760
// TODO: Don't block the JS event loop
let context = runtime.block_on(async { self.inner.lock().await });
try_or_reject_promise!(context.get_provider_factory(&chain_type))
};
let contract_decoder = Arc::clone(contract_decoder.as_inner());
runtime.clone().spawn_blocking(move || {
let result = factory
.create_provider(
runtime.clone(),
provider_config,
logger_config,
subscription_callback,
Arc::clone(&contract_decoder),
)
.map(|provider| {
Provider::new(
provider,
runtime,
contract_decoder,
#[cfg(feature = "scenarios")]
scenario_file,
)
});
deferred.resolve(|_env| result);
});
Ok(promise)
}
/// Registers a new provider factory for the provided chain type.
#[napi(catch_unwind)]
pub async fn register_provider_factory(
&self,
chain_type: String,
factory: &ProviderFactory,
) -> napi::Result<()> {
let mut context = self.inner.lock().await;
context.register_provider_factory(chain_type, factory.as_inner().clone());
Ok(())
}
#[napi(catch_unwind)]
pub async fn register_solidity_test_runner_factory(
&self,
chain_type: String,
factory: &SolidityTestRunnerFactory,
) -> napi::Result<()> {
let mut context = self.inner.lock().await;
context.register_solidity_test_runner(chain_type, factory.as_inner().clone());
Ok(())
}
/// Executes Solidity tests
///
/// The function will return a promise that resolves to a
/// [`SolidityTestResult`].
///
/// Arguments:
/// - `chainType`: the same chain type that was passed to
/// `registerProviderFactory`.
/// - `artifacts`: the project's compilation output artifacts. It's
/// important to include include all artifacts here, otherwise cheatcodes
/// that access artifacts and other functionality (e.g. auto-linking, gas
/// reports) can break.
/// - `testSuites`: the test suite ids that specify which test suites to
/// execute. The test suite artifacts must be present in `artifacts`.
/// - `configArgs`: solidity test runner configuration. See the struct docs
/// for details.
/// - `tracingConfig`: the build infos used for stack trace generation.
/// These are lazily parsed and it's important that they're passed as
/// Uint8 arrays for performance.
/// - `onTestSuiteCompletedCallback`: The progress callback will be called
/// with the results of each test suite as soon as it finished executing.
#[allow(clippy::too_many_arguments)]
#[napi(catch_unwind, ts_return_type = "Promise<SolidityTestResult>")]
pub fn run_solidity_tests(
&self,
env: Env,
chain_type: String,
artifacts: Vec<Artifact>,
test_suites: Vec<ArtifactId>,
config_args: SolidityTestRunnerConfigArgs,
tracing_config: TracingConfigWithBuffers,
#[napi(ts_arg_type = "(result: SuiteResult) => void")]
on_test_suite_completed_callback: JsFunction,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
let on_test_suite_completed_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> =
match on_test_suite_completed_callback.create_threadsafe_function(
// Unbounded queue size
0,
|ctx: ThreadSafeCallContext<SuiteResult>| Ok(vec![ctx.value]),
) {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let test_filter: Arc<TestFilterConfig> =
Arc::new(match config_args.try_get_test_filter() {
Ok(test_filter) => test_filter,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
});
let runtime = runtime::Handle::current();
let config = match config_args.resolve(&env, runtime.clone()) {
Ok(config) => config,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let context = self.inner.clone();
runtime.clone().spawn(async move {
macro_rules! try_or_reject_deferred {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return;
}
}
};
}
let factory = {
let context = context.lock().await;
try_or_reject_deferred!(context.solidity_test_runner_factory(&chain_type).await)
};
let linking_output =
try_or_reject_deferred!(LinkingOutput::link(&config.project_root, artifacts));
// Build revert decoder from ABIs of all artifacts.
let abis = linking_output
.known_contracts
.iter()
.map(|(_, contract)| &contract.abi);
let revert_decoder = RevertDecoder::new().with_abis(abis);
let test_suites = try_or_reject_deferred!(test_suites
.into_iter()
.map(edr_solidity::artifacts::ArtifactId::try_from)
.collect::<Result<Vec<_>, _>>());
let contracts = try_or_reject_deferred!(test_suites
.iter()
.map(|artifact_id| {
let contract_data = linking_output
.known_contracts
.get(artifact_id)
.ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Unknown contract: {}", artifact_id.identifier()),
)
})?;
let bytecode = contract_data.bytecode.clone().ok_or_else(|| {
napi::Error::new(
napi::Status::GenericFailure,
format!(
"No bytecode for test suite contract: {}",
artifact_id.identifier()
),
)
})?;
let test_contract = TestContract {
abi: contract_data.abi.clone(),
bytecode,
};
Ok((artifact_id.clone(), test_contract))
})
.collect::<napi::Result<TestContracts>>());
let include_traces = config.include_traces.into();
let runtime_for_factory = runtime.clone();
let test_runner = try_or_reject_deferred!(runtime
.clone()
.spawn_blocking(move || {
factory.create_test_runner(
runtime_for_factory,
config,
contracts,
linking_output.known_contracts,
linking_output.libs_to_deploy,
revert_decoder,
tracing_config.into(),
)
})
.await
.expect("Failed to join test runner factory thread"));
let runtime_for_runner = runtime.clone();
let test_result = try_or_reject_deferred!(runtime
.clone()
.spawn_blocking(move || {
test_runner.run_tests(
runtime_for_runner,
test_filter,
Arc::new(
move |SuiteResultAndArtifactId {
artifact_id,
result,
}| {
let suite_result =
SuiteResult::new(artifact_id, result, include_traces);
let status = on_test_suite_completed_callback
.call(suite_result, ThreadsafeFunctionCallMode::Blocking);
// This should always succeed since we're using an unbounded queue.
// We add an assertion for
// completeness.
assert_eq!(
status,
napi::Status::Ok,
"Failed to call on_test_suite_completed_callback with status: {status}"
);
},
),
)
})
.await
.expect("Failed to join test runner thread"));
deferred.resolve(move |_env| Ok(SolidityTestResult::from(test_result)));
});
Ok(promise)
}
}
pub struct Context {
provider_factories: HashMap<String, Arc<dyn SyncProviderFactory>>,
solidity_test_runner_factories: HashMap<String, Arc<dyn solidity::SyncTestRunnerFactory>>,
#[cfg(feature = "tracing")]
_tracing_write_guard: tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>,
}
impl Context {
/// Creates a new [`Context`] instance. Should only be called once!
pub fn new() -> napi::Result<Self> {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_target(false)
.with_level(true)
.with_filter(EnvFilter::from_default_env());
let subscriber = Registry::default().with(fmt_layer);
#[cfg(feature = "tracing")]
let (flame_layer, guard) = {
let (flame_layer, guard) = tracing_flame::FlameLayer::with_file("tracing.folded")
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create tracing.folded file with error: {err:?}"),
)
})?;
let flame_layer = flame_layer.with_empty_samples(false);
(flame_layer, guard)
};
#[cfg(feature = "tracing")]
let subscriber = subscriber.with(flame_layer);
if let Err(error) = tracing::subscriber::set_global_default(subscriber) {
println!(
"Failed to set global tracing subscriber with error: {error}\n\
Please only initialize EdrContext once per process to avoid this error."
);
}
Ok(Self {
provider_factories: HashMap::default(),
solidity_test_runner_factories: HashMap::default(),
#[cfg(feature = "tracing")]
_tracing_write_guard: guard,
})
}
/// Registers a new provider factory for the provided chain type.
pub fn register_provider_factory(
&mut self,
chain_type: String,
factory: Arc<dyn SyncProviderFactory>,
) {
self.provider_factories.insert(chain_type, factory);
}
pub fn register_solidity_test_runner(
&mut self,
chain_type: String,
factory: Arc<dyn solidity::SyncTestRunnerFactory>,
) {
self.solidity_test_runner_factories
.insert(chain_type, factory);
}
/// Tries to create a new provider for the provided chain type and
/// configuration.
pub fn get_provider_factory(
&self,
chain_type: &str,
) -> napi::Result<Arc<dyn SyncProviderFactory>> {
if let Some(factory) = self.provider_factories.get(chain_type) {
Ok(Arc::clone(factory))
} else {
Err(napi::Error::new(
napi::Status::GenericFailure,
"Provider for provided chain type does not exist",
))
}
}
pub async fn solidity_test_runner_factory(
&self,
chain_type: &str,
) -> napi::Result<Arc<dyn solidity::SyncTestRunnerFactory>> {
if let Some(factory) = self.solidity_test_runner_factories.get(chain_type) {
Ok(Arc::clone(factory))
} else {
Err(napi::Error::new(
napi::Status::GenericFailure,
"Solidity test runner for provided chain type does not exist",
))
}
}
}

View File

@@ -0,0 +1,57 @@
use std::sync::Arc;
use napi_derive::napi;
use crate::config::TracingConfigWithBuffers;
#[napi]
pub struct ContractDecoder {
inner: Arc<edr_solidity::contract_decoder::ContractDecoder>,
}
#[napi]
impl ContractDecoder {
#[doc = "Creates an empty instance."]
#[napi(constructor, catch_unwind)]
// Following TS convention for the constructor without arguments to be `new()`.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
inner: Arc::new(edr_solidity::contract_decoder::ContractDecoder::default()),
}
}
#[doc = "Creates a new instance with the provided configuration."]
#[napi(factory, catch_unwind)]
pub fn with_contracts(config: TracingConfigWithBuffers) -> napi::Result<Self> {
let build_info_config = edr_solidity::artifacts::BuildInfoConfig::parse_from_buffers(
(&edr_napi_core::solidity::config::TracingConfigWithBuffers::from(config)).into(),
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let contract_decoder =
edr_solidity::contract_decoder::ContractDecoder::new(&build_info_config).map_or_else(
|error| Err(napi::Error::from_reason(error.to_string())),
|contract_decoder| Ok(Arc::new(contract_decoder)),
)?;
Ok(Self {
inner: contract_decoder,
})
}
}
impl ContractDecoder {
/// Returns a reference to the inner contract decoder.
pub fn as_inner(&self) -> &Arc<edr_solidity::contract_decoder::ContractDecoder> {
&self.inner
}
}
impl From<Arc<edr_solidity::contract_decoder::ContractDecoder>> for ContractDecoder {
fn from(contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>) -> Self {
Self {
inner: contract_decoder,
}
}
}

40
dev/env/node_modules/@nomicfoundation/edr/src/debug_trace.rs generated vendored Executable file
View File

@@ -0,0 +1,40 @@
use std::collections::HashMap;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
// False positive: imported by HH2
#[allow(dead_code)]
#[napi(object)]
pub struct DebugTraceResult {
pub pass: bool,
pub gas_used: BigInt,
pub output: Option<Uint8Array>,
pub struct_logs: Vec<DebugTraceLogItem>,
}
#[napi(object)]
pub struct DebugTraceLogItem {
/// Program Counter
pub pc: BigInt,
// Op code
pub op: u8,
/// Gas left before executing this operation as hex number.
pub gas: String,
/// Gas cost of this operation as hex number.
pub gas_cost: String,
/// Array of all values (hex numbers) on the stack
pub stack: Option<Vec<String>>,
/// Depth of the call stack
pub depth: BigInt,
/// Size of memory array
pub mem_size: BigInt,
/// Name of the operation
pub op_name: String,
/// Description of an error as a hex string.
pub error: Option<String>,
/// Array of all allocated values as hex strings.
pub memory: Option<Vec<String>>,
/// Map of all stored values with keys and values encoded as hex strings.
pub storage: Option<HashMap<String, String>>,
}

92
dev/env/node_modules/@nomicfoundation/edr/src/gas_report.rs generated vendored Executable file
View File

@@ -0,0 +1,92 @@
use std::collections::HashMap;
use napi::bindgen_prelude::BigInt;
use napi_derive::napi;
#[napi(object)]
pub struct GasReport {
pub contracts: HashMap<String, ContractGasReport>,
}
#[napi(object)]
pub struct ContractGasReport {
pub deployments: Vec<DeploymentGasReport>,
pub functions: HashMap<String, Vec<FunctionGasReport>>,
}
#[napi]
pub enum GasReportExecutionStatus {
Success,
Revert,
Halt,
}
#[napi(object)]
pub struct DeploymentGasReport {
pub gas: BigInt,
pub size: BigInt,
pub status: GasReportExecutionStatus,
}
#[napi(object)]
pub struct FunctionGasReport {
pub gas: BigInt,
pub status: GasReportExecutionStatus,
}
impl From<edr_gas_report::GasReport> for GasReport {
fn from(value: edr_gas_report::GasReport) -> Self {
Self {
contracts: value
.into_inner()
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}
impl From<edr_gas_report::ContractGasReport> for ContractGasReport {
fn from(value: edr_gas_report::ContractGasReport) -> Self {
Self {
deployments: value.deployments.into_iter().map(Into::into).collect(),
functions: value
.functions
.into_iter()
.map(|(k, v)| {
let function_reports = v.into_iter().map(FunctionGasReport::from).collect();
(k, function_reports)
})
.collect(),
}
}
}
impl From<edr_gas_report::GasReportExecutionStatus> for GasReportExecutionStatus {
fn from(value: edr_gas_report::GasReportExecutionStatus) -> Self {
match value {
edr_gas_report::GasReportExecutionStatus::Success => Self::Success,
edr_gas_report::GasReportExecutionStatus::Revert => Self::Revert,
edr_gas_report::GasReportExecutionStatus::Halt => Self::Halt,
}
}
}
impl From<edr_gas_report::DeploymentGasReport> for DeploymentGasReport {
fn from(value: edr_gas_report::DeploymentGasReport) -> Self {
Self {
gas: BigInt::from(value.gas),
size: BigInt::from(value.size),
status: value.status.into(),
}
}
}
impl From<edr_gas_report::FunctionGasReport> for FunctionGasReport {
fn from(value: edr_gas_report::FunctionGasReport) -> Self {
Self {
gas: BigInt::from(value.gas),
status: value.status.into(),
}
}
}

115
dev/env/node_modules/@nomicfoundation/edr/src/instrument.rs generated vendored Executable file
View File

@@ -0,0 +1,115 @@
use edr_instrument::coverage::{self, Version};
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
#[napi(object)]
pub struct InstrumentationResult {
/// The generated source code with coverage instrumentation.
#[napi(readonly)]
pub source: String,
/// The metadata for each instrumented code segment.
#[napi(readonly)]
pub metadata: Vec<InstrumentationMetadata>,
}
impl TryFrom<edr_instrument::coverage::InstrumentationResult> for InstrumentationResult {
type Error = usize;
fn try_from(
value: edr_instrument::coverage::InstrumentationResult,
) -> Result<Self, Self::Error> {
let metadata = value
.metadata
.into_iter()
.map(InstrumentationMetadata::try_from)
.collect::<Result<Vec<_>, _>>()?;
Ok(InstrumentationResult {
source: value.source,
metadata,
})
}
}
#[napi(object)]
pub struct InstrumentationMetadata {
/// The tag that identifies the instrumented code. Tags are
/// deterministically generated from the source code, source id, and
/// Solidity version.
#[napi(readonly)]
pub tag: Uint8Array,
/// The kind of instrumented code. Currently, the only supported kind
/// is "statement".
#[napi(readonly)]
pub kind: String,
/// The starting position of the instrumented code - including trivia such
/// as whitespace - in the source code, in UTF-16 code units.
#[napi(readonly)]
pub start_utf16: i64,
/// The ending position of the instrumented code - including trivia such as
/// whitespace - in the source code, in UTF-16 code units.
#[napi(readonly)]
pub end_utf16: i64,
}
impl TryFrom<edr_instrument::coverage::InstrumentationMetadata> for InstrumentationMetadata {
type Error = usize;
fn try_from(
value: edr_instrument::coverage::InstrumentationMetadata,
) -> Result<Self, Self::Error> {
let start_utf16 = value
.start_utf16
.try_into()
.map_err(|_error| value.start_utf16)?;
let end_utf16 = value
.end_utf16
.try_into()
.map_err(|_error| value.end_utf16)?;
Ok(InstrumentationMetadata {
tag: Uint8Array::with_data_copied(value.tag),
kind: value.kind.to_owned(),
start_utf16,
end_utf16,
})
}
}
/// Adds per-statement coverage instrumentation to the given Solidity source
/// code.
#[napi(catch_unwind)]
pub fn add_statement_coverage_instrumentation(
source_code: String,
source_id: String,
solidity_version: String,
coverage_library_path: String,
) -> napi::Result<InstrumentationResult> {
let solidity_version = Version::parse(&solidity_version).map_err(|error| {
napi::Error::new(
napi::Status::InvalidArg,
format!("Invalid Solidity version: {error}"),
)
})?;
let instrumented = coverage::instrument_code(
&source_code,
&source_id,
solidity_version,
&coverage_library_path,
)
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error))?;
instrumented.try_into().map_err(|location| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Cannot represent source locations in JavaScript: {location}."),
)
})
}
/// Retrieves the latest version of `Solidity` supported for instrumentation.
#[napi(catch_unwind)]
pub fn latest_supported_solidity_version() -> String {
edr_instrument::LATEST_SUPPORTED_SOLIDITY_VERSION.to_string()
}

50
dev/env/node_modules/@nomicfoundation/edr/src/lib.rs generated vendored Executable file
View File

@@ -0,0 +1,50 @@
// #![warn(missing_docs)]
//! NAPI bindings for EDR's core types.
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod account;
mod block;
/// Types for overriding a call.
pub mod call_override;
/// Types for casting N-API types to Rust types.
pub mod cast;
/// Supported chain types.
pub mod chains;
/// Types for configuration.
pub mod config;
/// Types related to an EDR N-API context.
pub mod context;
/// Types for decoding smart contract data.
pub mod contract_decoder;
mod debug_trace;
pub mod gas_report;
/// Types and functions related to code coverage instrumentation.
pub mod instrument;
/// Types for EVM execution logs.
pub mod log;
/// Types for an RPC request logger.
pub mod logger;
/// Types for mocking provider behavior.
#[cfg(feature = "test-mock")]
pub mod mock;
/// Types for precompiles.
pub mod precompile;
/// Types for Ethereum RPC providers.
pub mod provider;
/// Types for EVM execution results.
pub mod result;
/// Types relating to benchmark scenarios.
#[cfg(feature = "scenarios")]
pub mod scenarios;
mod serde;
/// Solidity test runner.
pub mod solidity_tests;
/// Types for subscribing to events.
pub mod subscription;
/// Types for EVM traces.
pub mod trace;
/// Types related to Ethereum withdrawals.
pub mod withdrawal;

28
dev/env/node_modules/@nomicfoundation/edr/src/log.rs generated vendored Executable file
View File

@@ -0,0 +1,28 @@
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
/// Ethereum execution log.
#[napi(object)]
pub struct ExecutionLog {
pub address: Uint8Array,
pub topics: Vec<Uint8Array>,
pub data: Uint8Array,
}
impl From<&edr_receipt::log::ExecutionLog> for ExecutionLog {
fn from(value: &edr_receipt::log::ExecutionLog) -> Self {
let topics = value
.topics()
.iter()
.map(Uint8Array::with_data_copied)
.collect();
let data = Uint8Array::with_data_copied(&value.data.data);
Self {
address: Uint8Array::with_data_copied(value.address),
topics,
data,
}
}
}

120
dev/env/node_modules/@nomicfoundation/edr/src/logger.rs generated vendored Executable file
View File

@@ -0,0 +1,120 @@
use std::sync::{mpsc::channel, Arc};
use edr_napi_core::logger::LoggerError;
use edr_primitives::Bytes;
use napi::{
threadsafe_function::{
ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
},
JsFunction, Status,
};
use napi_derive::napi;
#[napi(object)]
pub struct LoggerConfig {
/// Whether to enable the logger.
pub enable: bool,
#[napi(ts_type = "(inputs: ArrayBuffer[]) => string[]")]
pub decode_console_log_inputs_callback: JsFunction,
#[napi(ts_type = "(message: string, replace: boolean) => void")]
pub print_line_callback: JsFunction,
}
impl LoggerConfig {
/// Resolves the logger config, converting it to a
/// `edr_napi_core::logger::Config`.
pub fn resolve(self, env: &napi::Env) -> napi::Result<edr_napi_core::logger::Config> {
let mut decode_console_log_inputs_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> =
self.decode_console_log_inputs_callback
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<Vec<Bytes>>| {
let inputs = ctx.env.create_array_with_length(ctx.value.len()).and_then(
|mut inputs| {
for (idx, input) in ctx.value.into_iter().enumerate() {
ctx.env
.create_arraybuffer_with_data(input.to_vec())
.and_then(|input| {
inputs.set_element(idx as u32, input.into_raw())
})?;
}
Ok(inputs)
},
)?;
Ok(vec![inputs])
})?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
decode_console_log_inputs_callback.unref(env)?;
let decode_console_log_inputs_fn = Arc::new(move |console_log_inputs| {
let (sender, receiver) = channel();
let status = decode_console_log_inputs_callback.call_with_return_value(
console_log_inputs,
ThreadsafeFunctionCallMode::Blocking,
move |decoded_inputs: Vec<String>| {
sender.send(decoded_inputs).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from decode_console_log_inputs",
)
})
},
);
assert_eq!(status, Status::Ok);
receiver
.recv()
.expect("Receive can only fail if the channel is closed")
});
let mut print_line_callback: ThreadsafeFunction<_, ErrorStrategy::Fatal> = self
.print_line_callback
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<(String, bool)>| {
// String
let message = ctx.env.create_string_from_std(ctx.value.0)?;
// bool
let replace = ctx.env.get_boolean(ctx.value.1)?;
Ok(vec![message.into_unknown(), replace.into_unknown()])
})?;
// Maintain a weak reference to the function to avoid blocking the event loop
// from exiting.
print_line_callback.unref(env)?;
let print_line_fn = Arc::new(move |message, replace| {
let (sender, receiver) = channel();
let status = print_line_callback.call_with_return_value(
(message, replace),
ThreadsafeFunctionCallMode::Blocking,
move |()| {
sender.send(()).map_err(|_error| {
napi::Error::new(
Status::GenericFailure,
"Failed to send result from decode_console_log_inputs",
)
})
},
);
let () = receiver.recv().unwrap();
if status == napi::Status::Ok {
Ok(())
} else {
Err(LoggerError::PrintLine)
}
});
Ok(edr_napi_core::logger::Config {
enable: self.enable,
decode_console_log_inputs_fn,
print_line_fn,
})
}
}

71
dev/env/node_modules/@nomicfoundation/edr/src/mock.rs generated vendored Executable file
View File

@@ -0,0 +1,71 @@
pub mod time;
use std::sync::Arc;
use edr_chain_spec::EvmHaltReason;
use edr_napi_core::provider::SyncProvider;
use edr_rpc_client::jsonrpc;
use edr_solidity::contract_decoder::ContractDecoder;
use napi::tokio::runtime;
use napi_derive::napi;
use crate::{context::EdrContext, provider::Provider};
/// A mock provider that always returns the given mocked response.
pub struct MockProvider {
mocked_response: serde_json::Value,
}
impl MockProvider {
pub fn new(mocked_response: serde_json::Value) -> Self {
Self { mocked_response }
}
}
impl SyncProvider for MockProvider {
fn handle_request(
&self,
_request: String,
_contract_decoder: Arc<ContractDecoder>,
) -> napi::Result<edr_napi_core::spec::Response<EvmHaltReason>> {
let response = jsonrpc::ResponseData::Success {
result: self.mocked_response.clone(),
};
edr_napi_core::spec::marshal_response_data(response)
.map(|data| edr_napi_core::spec::Response {
solidity_trace: None,
data,
traces: Vec::new(),
})
.map_err(|error| napi::Error::new(napi::Status::GenericFailure, error.to_string()))
}
fn set_call_override_callback(
&self,
_call_override_callback: Arc<dyn edr_provider::SyncCallOverride>,
) {
}
fn set_verbose_tracing(&self, _enabled: bool) {}
}
#[napi]
impl EdrContext {
#[doc = "Creates a mock provider, which always returns the given response."]
#[doc = "For testing purposes."]
#[napi]
pub fn create_mock_provider(
&self,
mocked_response: serde_json::Value,
) -> napi::Result<Provider> {
let provider = Provider::new(
Arc::new(MockProvider::new(mocked_response)),
runtime::Handle::current(),
Arc::new(ContractDecoder::default()),
#[cfg(feature = "scenarios")]
None,
);
Ok(provider)
}
}

134
dev/env/node_modules/@nomicfoundation/edr/src/mock/time.rs generated vendored Executable file
View File

@@ -0,0 +1,134 @@
use std::sync::Arc;
use edr_chain_spec::ChainSpec;
use edr_chain_spec_block::BlockChainSpec;
use edr_chain_spec_rpc::RpcBlockChainSpec;
use edr_generic::GenericChainSpec;
use edr_napi_core::logger::Logger;
use edr_primitives::B256;
use napi::{bindgen_prelude::BigInt, tokio::runtime, Env, JsObject};
use napi_derive::napi;
use crate::{
cast::TryCast as _,
config::{resolve_configs, ConfigResolution, ProviderConfig},
contract_decoder::ContractDecoder,
logger::LoggerConfig,
provider::Provider,
subscription::SubscriptionConfig,
};
#[napi]
pub struct MockTime {
inner: Arc<edr_provider::time::MockTime>,
}
#[napi]
impl MockTime {
#[doc = "Creates a new instance of `MockTime` with the current time."]
#[napi(factory, catch_unwind)]
pub fn now() -> Self {
Self {
inner: Arc::new(edr_provider::time::MockTime::now()),
}
}
#[doc = "Adds the specified number of seconds to the current time."]
#[napi(catch_unwind)]
pub fn add_seconds(&self, seconds: BigInt) -> napi::Result<()> {
let seconds = seconds.try_cast()?;
self.inner.add_seconds(seconds);
Ok(())
}
}
#[doc = "Creates a provider with a mock timer."]
#[doc = "For testing purposes."]
#[napi(catch_unwind, ts_return_type = "Promise<Provider>")]
pub fn create_provider_with_mock_timer(
env: Env,
provider_config: ProviderConfig,
logger_config: LoggerConfig,
subscription_config: SubscriptionConfig,
contract_decoder: &ContractDecoder,
time: &MockTime,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
macro_rules! try_or_reject_promise {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
}
};
}
let runtime = runtime::Handle::current();
let ConfigResolution {
logger_config,
provider_config,
subscription_callback,
} = try_or_reject_promise!(resolve_configs(
&env,
runtime.clone(),
provider_config,
logger_config,
subscription_config,
));
let contract_decoder = Arc::clone(contract_decoder.as_inner());
let timer = Arc::clone(&time.inner);
runtime.clone().spawn_blocking(move || {
// Using a closure to limit the scope, allowing us to use `?` for error
// handling. This is necessary because the result of the closure is used
// to resolve the deferred promise.
let create_provider = move || -> napi::Result<Provider> {
let logger = Logger::<GenericChainSpec, Arc<edr_provider::time::MockTime>>::new(
logger_config,
Arc::clone(&contract_decoder),
)?;
let provider_config =
edr_provider::ProviderConfig::<edr_chain_l1::Hardfork>::try_from(provider_config)?;
let provider =
edr_provider::Provider::<GenericChainSpec, Arc<edr_provider::time::MockTime>>::new(
runtime.clone(),
Box::new(logger),
Box::new(move |event| {
let event = edr_napi_core::subscription::SubscriptionEvent::new::<
<GenericChainSpec as BlockChainSpec>::Block,
<GenericChainSpec as RpcBlockChainSpec>::RpcBlock<B256>,
<GenericChainSpec as ChainSpec>::SignedTransaction,
>(event);
subscription_callback.call(event);
}),
provider_config,
Arc::clone(&contract_decoder),
timer,
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
Ok(Provider::new(
Arc::new(provider),
runtime,
contract_decoder,
#[cfg(feature = "scenarios")]
None,
))
};
let result = create_provider();
deferred.resolve(|_env| result);
});
Ok(promise)
}

50
dev/env/node_modules/@nomicfoundation/edr/src/precompile.rs generated vendored Executable file
View File

@@ -0,0 +1,50 @@
use edr_precompile::PrecompileFn;
use edr_primitives::Address;
use napi::bindgen_prelude::Uint8Array;
use napi_derive::napi;
#[napi]
#[derive(Clone)]
pub struct Precompile {
address: Address,
precompile_fn: PrecompileFn,
}
impl Precompile {
pub fn new(address: Address, precompile_fn: PrecompileFn) -> Self {
Self {
address,
precompile_fn,
}
}
/// Returns the address and precompile function as a tuple.
pub fn to_tuple(&self) -> (Address, PrecompileFn) {
(self.address, self.precompile_fn)
}
}
impl From<edr_precompile::Precompile> for Precompile {
fn from(value: edr_precompile::Precompile) -> Self {
Self {
address: *value.address(),
precompile_fn: value.into_precompile(),
}
}
}
#[napi]
impl Precompile {
/// Returns the address of the precompile.
#[napi(catch_unwind, getter)]
pub fn address(&self) -> Uint8Array {
Uint8Array::with_data_copied(self.address)
}
}
/// [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md#specification)
/// secp256r1 precompile.
#[napi(catch_unwind)]
pub fn precompile_p256_verify() -> Precompile {
Precompile::from(edr_precompile::secp256r1::P256VERIFY)
}

162
dev/env/node_modules/@nomicfoundation/edr/src/provider.rs generated vendored Executable file
View File

@@ -0,0 +1,162 @@
/// Types related to provider factories.
pub mod factory;
mod response;
use std::sync::Arc;
use edr_napi_core::provider::SyncProvider;
use edr_solidity::compiler::create_models_and_decode_bytecodes;
use napi::{tokio::runtime, Env, JsFunction, JsObject, Status};
use napi_derive::napi;
pub use self::factory::ProviderFactory;
use self::response::Response;
use crate::{call_override::CallOverrideCallback, contract_decoder::ContractDecoder};
/// A JSON-RPC provider for Ethereum.
#[napi]
pub struct Provider {
contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>,
provider: Arc<dyn SyncProvider>,
runtime: runtime::Handle,
#[cfg(feature = "scenarios")]
scenario_file: Option<napi::tokio::sync::Mutex<napi::tokio::fs::File>>,
}
impl Provider {
/// Constructs a new instance.
pub fn new(
provider: Arc<dyn SyncProvider>,
runtime: runtime::Handle,
contract_decoder: Arc<edr_solidity::contract_decoder::ContractDecoder>,
#[cfg(feature = "scenarios")] scenario_file: Option<
napi::tokio::sync::Mutex<napi::tokio::fs::File>,
>,
) -> Self {
Self {
contract_decoder,
provider,
runtime,
#[cfg(feature = "scenarios")]
scenario_file,
}
}
}
#[napi]
impl Provider {
#[doc = "Adds a compilation result to the instance."]
#[doc = ""]
#[doc = "For internal use only. Support for this method may be removed in the future."]
#[napi(catch_unwind)]
pub async fn add_compilation_result(
&self,
solc_version: String,
compiler_input: serde_json::Value,
compiler_output: serde_json::Value,
) -> napi::Result<()> {
let contract_decoder = self.contract_decoder.clone();
self.runtime
.spawn_blocking(move || {
let compiler_input = serde_json::from_value(compiler_input)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let compiler_output = serde_json::from_value(compiler_output)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let contracts = match create_models_and_decode_bytecodes(
solc_version,
&compiler_input,
&compiler_output,
) {
Ok(contracts) => contracts,
Err(error) => {
return Err(napi::Error::from_reason(format!("Contract decoder failed to be updated. Please report this to help us improve Hardhat.\n{error}")));
}
};
for contract in contracts {
contract_decoder.add_contract_metadata(contract);
}
Ok(())
})
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))?
}
#[doc = "Retrieves the instance's contract decoder."]
#[napi(catch_unwind)]
pub fn contract_decoder(&self) -> ContractDecoder {
ContractDecoder::from(Arc::clone(&self.contract_decoder))
}
#[doc = "Handles a JSON-RPC request and returns a JSON-RPC response."]
#[napi(catch_unwind)]
pub async fn handle_request(&self, request: String) -> napi::Result<Response> {
let provider = self.provider.clone();
#[cfg(feature = "scenarios")]
if let Some(scenario_file) = &self.scenario_file {
crate::scenarios::write_request(scenario_file, &request).await?;
}
let contract_decoder = Arc::clone(&self.contract_decoder);
self.runtime
.spawn_blocking(move || provider.handle_request(request, contract_decoder))
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))?
.map(Response::from)
}
#[napi(catch_unwind, ts_return_type = "Promise<void>")]
pub fn set_call_override_callback(
&self,
env: Env,
#[napi(
ts_arg_type = "(contract_address: ArrayBuffer, data: ArrayBuffer) => Promise<CallOverrideResult | undefined>"
)]
call_override_callback: JsFunction,
) -> napi::Result<JsObject> {
let (deferred, promise) = env.create_deferred()?;
let call_override_callback =
match CallOverrideCallback::new(&env, call_override_callback, self.runtime.clone()) {
Ok(callback) => callback,
Err(error) => {
deferred.reject(error);
return Ok(promise);
}
};
let call_override_callback =
Arc::new(move |address, data| call_override_callback.call_override(address, data));
let provider = self.provider.clone();
self.runtime.spawn_blocking(move || {
provider.set_call_override_callback(call_override_callback);
deferred.resolve(|_env| Ok(()));
});
Ok(promise)
}
/// Set to `true` to make the traces returned with `eth_call`,
/// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`,
/// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to
/// `false` to disable this.
#[napi(catch_unwind)]
pub async fn set_verbose_tracing(&self, verbose_tracing: bool) -> napi::Result<()> {
let provider = self.provider.clone();
self.runtime
.spawn_blocking(move || {
provider.set_verbose_tracing(verbose_tracing);
})
.await
.map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string()))
}
}

View File

@@ -0,0 +1,22 @@
use std::sync::Arc;
use edr_napi_core::provider::SyncProviderFactory;
use napi_derive::napi;
#[napi]
pub struct ProviderFactory {
inner: Arc<dyn SyncProviderFactory>,
}
impl ProviderFactory {
/// Returns a reference to the inner provider factory.
pub fn as_inner(&self) -> &Arc<dyn SyncProviderFactory> {
&self.inner
}
}
impl From<Arc<dyn SyncProviderFactory>> for ProviderFactory {
fn from(inner: Arc<dyn SyncProviderFactory>) -> Self {
Self { inner }
}
}

View File

@@ -0,0 +1,73 @@
use edr_chain_spec::EvmHaltReason;
use edr_napi_core::spec::SolidityTraceData;
use edr_solidity::contract_decoder::NestedTraceDecoder as _;
use napi::Either;
use napi_derive::napi;
use crate::{
cast::TryCast,
trace::{solidity_stack_trace::SolidityStackTrace, RawTrace},
};
#[napi]
pub struct Response {
inner: edr_napi_core::spec::Response<EvmHaltReason>,
}
impl From<edr_napi_core::spec::Response<EvmHaltReason>> for Response {
fn from(value: edr_napi_core::spec::Response<EvmHaltReason>) -> Self {
Self { inner: value }
}
}
#[napi]
impl Response {
#[doc = "Returns the response data as a JSON string or a JSON object."]
#[napi(catch_unwind, getter)]
pub fn data(&self) -> Either<String, serde_json::Value> {
self.inner.data.clone()
}
// Rust port of https://github.com/NomicFoundation/hardhat/blob/c20bf195a6efdc2d74e778b7a4a7799aac224841/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts#L590
#[doc = "Compute the error stack trace. Return the stack trace if it can be decoded, otherwise returns none. Throws if there was an error computing the stack trace."]
#[napi(catch_unwind)]
pub fn stack_trace(&self) -> napi::Result<Option<SolidityStackTrace>> {
let Some(SolidityTraceData {
trace,
contract_decoder,
}) = &self.inner.solidity_trace
else {
return Ok(None);
};
let nested_trace = edr_solidity::nested_tracer::convert_trace_messages_to_nested_trace(
trace.as_ref().clone(),
)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
if let Some(vm_trace) = nested_trace {
let decoded_trace = contract_decoder
.try_to_decode_nested_trace(vm_trace)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
let stack_trace = edr_solidity::solidity_tracer::get_stack_trace(decoded_trace)
.map_err(|err| napi::Error::from_reason(err.to_string()))?;
let stack_trace = stack_trace
.into_iter()
.map(TryCast::try_cast)
.collect::<Result<Vec<_>, _>>()?;
Ok(Some(stack_trace))
} else {
Ok(None)
}
}
#[doc = "Returns the raw traces of executed contracts. This maybe contain zero or more traces."]
#[napi(catch_unwind, getter)]
pub fn traces(&self) -> Vec<RawTrace> {
self.inner
.traces
.iter()
.map(|trace| RawTrace::from(trace.clone()))
.collect()
}
}

216
dev/env/node_modules/@nomicfoundation/edr/src/result.rs generated vendored Executable file
View File

@@ -0,0 +1,216 @@
use edr_chain_spec::EvmHaltReason;
use edr_tracing::AfterMessage;
use napi::{
bindgen_prelude::{BigInt, Either3, Uint8Array},
Either,
};
use napi_derive::napi;
use crate::log::ExecutionLog;
/// The possible reasons for successful termination of the EVM.
#[napi]
pub enum SuccessReason {
/// The opcode `STOP` was called
Stop,
/// The opcode `RETURN` was called
Return,
/// The opcode `SELFDESTRUCT` was called
SelfDestruct,
}
impl From<edr_chain_spec_evm::result::SuccessReason> for SuccessReason {
fn from(eval: edr_chain_spec_evm::result::SuccessReason) -> Self {
match eval {
edr_chain_spec_evm::result::SuccessReason::Stop => Self::Stop,
edr_chain_spec_evm::result::SuccessReason::Return => Self::Return,
edr_chain_spec_evm::result::SuccessReason::SelfDestruct => Self::SelfDestruct,
}
}
}
impl From<SuccessReason> for edr_chain_spec_evm::result::SuccessReason {
fn from(value: SuccessReason) -> Self {
match value {
SuccessReason::Stop => Self::Stop,
SuccessReason::Return => Self::Return,
SuccessReason::SelfDestruct => Self::SelfDestruct,
}
}
}
#[napi(object)]
pub struct CallOutput {
/// Return value
pub return_value: Uint8Array,
}
#[napi(object)]
pub struct CreateOutput {
/// Return value
pub return_value: Uint8Array,
/// Optionally, a 160-bit address
pub address: Option<Uint8Array>,
}
/// The result when the EVM terminates successfully.
#[napi(object)]
pub struct SuccessResult {
/// The reason for termination
pub reason: SuccessReason,
/// The amount of gas used
pub gas_used: BigInt,
/// The amount of gas refunded
pub gas_refunded: BigInt,
/// The logs
pub logs: Vec<ExecutionLog>,
/// The transaction output
pub output: Either<CallOutput, CreateOutput>,
}
/// The result when the EVM terminates due to a revert.
#[napi(object)]
pub struct RevertResult {
/// The amount of gas used
pub gas_used: BigInt,
/// The transaction output
pub output: Uint8Array,
}
/// Indicates that the EVM has experienced an exceptional halt. This causes
/// execution to immediately end with all gas being consumed.
#[napi]
pub enum ExceptionalHalt {
OutOfGas,
OpcodeNotFound,
InvalidFEOpcode,
InvalidJump,
NotActivated,
StackUnderflow,
StackOverflow,
OutOfOffset,
CreateCollision,
PrecompileError,
NonceOverflow,
/// Create init code size exceeds limit (runtime).
CreateContractSizeLimit,
/// Error on created contract that begins with EF
CreateContractStartingWithEF,
/// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
CreateInitCodeSizeLimit,
}
impl From<EvmHaltReason> for ExceptionalHalt {
fn from(halt: EvmHaltReason) -> Self {
match halt {
EvmHaltReason::OutOfGas(..) => ExceptionalHalt::OutOfGas,
EvmHaltReason::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound,
EvmHaltReason::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode,
EvmHaltReason::InvalidJump => ExceptionalHalt::InvalidJump,
EvmHaltReason::NotActivated => ExceptionalHalt::NotActivated,
EvmHaltReason::StackUnderflow => ExceptionalHalt::StackUnderflow,
EvmHaltReason::StackOverflow => ExceptionalHalt::StackOverflow,
EvmHaltReason::OutOfOffset => ExceptionalHalt::OutOfOffset,
EvmHaltReason::CreateCollision => ExceptionalHalt::CreateCollision,
EvmHaltReason::PrecompileError | EvmHaltReason::PrecompileErrorWithContext(_) => {
ExceptionalHalt::PrecompileError
}
EvmHaltReason::NonceOverflow => ExceptionalHalt::NonceOverflow,
EvmHaltReason::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit,
EvmHaltReason::CreateContractStartingWithEF => {
ExceptionalHalt::CreateContractStartingWithEF
}
EvmHaltReason::CreateInitCodeSizeLimit => ExceptionalHalt::CreateInitCodeSizeLimit,
EvmHaltReason::OverflowPayment
| EvmHaltReason::StateChangeDuringStaticCall
| EvmHaltReason::CallNotAllowedInsideStatic
| EvmHaltReason::OutOfFunds
| EvmHaltReason::CallTooDeep => {
unreachable!("Internal halts that can be only found inside Inspector: {halt:?}")
}
}
}
}
/// The result when the EVM terminates due to an exceptional halt.
#[napi(object)]
pub struct HaltResult {
/// The exceptional halt that occurred
pub reason: ExceptionalHalt,
/// Halting will spend all the gas and will thus be equal to the specified
/// gas limit
pub gas_used: BigInt,
}
/// The result of executing a transaction.
#[napi(object)]
pub struct ExecutionResult {
/// The transaction result
pub result: Either3<SuccessResult, RevertResult, HaltResult>,
/// Optional contract address if the transaction created a new contract.
pub contract_address: Option<Uint8Array>,
}
impl From<&AfterMessage<EvmHaltReason>> for ExecutionResult {
fn from(value: &AfterMessage<EvmHaltReason>) -> Self {
let AfterMessage {
execution_result,
contract_address,
} = value;
let result = match execution_result {
edr_chain_spec_evm::result::ExecutionResult::Success {
reason,
gas_used,
gas_refunded,
logs,
output,
} => {
let logs = logs.iter().map(ExecutionLog::from).collect();
Either3::A(SuccessResult {
reason: SuccessReason::from(*reason),
gas_used: BigInt::from(*gas_used),
gas_refunded: BigInt::from(*gas_refunded),
logs,
output: match output {
edr_chain_spec_evm::result::Output::Call(return_value) => {
let return_value = Uint8Array::with_data_copied(return_value);
Either::A(CallOutput { return_value })
}
edr_chain_spec_evm::result::Output::Create(return_value, address) => {
let return_value = Uint8Array::with_data_copied(return_value);
Either::B(CreateOutput {
return_value,
address: address.as_ref().map(Uint8Array::with_data_copied),
})
}
},
})
}
edr_chain_spec_evm::result::ExecutionResult::Revert { gas_used, output } => {
let output = Uint8Array::with_data_copied(output);
Either3::B(RevertResult {
gas_used: BigInt::from(*gas_used),
output,
})
}
edr_chain_spec_evm::result::ExecutionResult::Halt { reason, gas_used } => {
Either3::C(HaltResult {
reason: ExceptionalHalt::from(reason.clone()),
gas_used: BigInt::from(*gas_used),
})
}
};
let contract_address = contract_address.as_ref().map(Uint8Array::with_data_copied);
Self {
result,
contract_address,
}
}
}

53
dev/env/node_modules/@nomicfoundation/edr/src/scenarios.rs generated vendored Executable file
View File

@@ -0,0 +1,53 @@
use std::time::{SystemTime, UNIX_EPOCH};
use edr_scenarios::ScenarioConfig;
use napi::tokio::{fs::File, io::AsyncWriteExt, sync::Mutex};
use rand::{distributions::Alphanumeric, Rng};
const SCENARIO_FILE_PREFIX: &str = "EDR_SCENARIO_PREFIX";
/// Creates a scenario file with the provided configuration.
pub async fn scenario_file(
chain_type: String,
provider_config: edr_napi_core::provider::Config,
logger_enabled: bool,
) -> napi::Result<Option<Mutex<File>>> {
if let Ok(scenario_prefix) = std::env::var(SCENARIO_FILE_PREFIX) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let suffix = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(4)
.map(char::from)
.collect::<String>();
let mut scenario_file =
File::create(format!("{scenario_prefix}_{timestamp}_{suffix}.json")).await?;
let config = ScenarioConfig {
chain_type: Some(chain_type),
logger_enabled,
provider_config: provider_config.try_into()?,
};
let mut line = serde_json::to_string(&config)?;
line.push('\n');
scenario_file.write_all(line.as_bytes()).await?;
Ok(Some(Mutex::new(scenario_file)))
} else {
Ok(None)
}
}
/// Writes a JSON-RPC request to the scenario file.
pub async fn write_request(scenario_file: &Mutex<File>, request: &str) -> napi::Result<()> {
let mut line = request.to_string();
line.push('\n');
{
let mut scenario_file = scenario_file.lock().await;
scenario_file.write_all(line.as_bytes()).await?;
}
Ok(())
}

57
dev/env/node_modules/@nomicfoundation/edr/src/serde.rs generated vendored Executable file
View File

@@ -0,0 +1,57 @@
use edr_primitives::hex;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use serde::Serializer;
/// Serialize a `Uint8Array` as a 0x-prefixed hex string
pub fn serialize_uint8array_as_hex<S>(buffer: &Uint8Array, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_string = format!("0x{}", hex::encode(buffer));
serializer.serialize_str(&hex_string)
}
/// Serialize an Option<Uint8Array> as a 0x-prefixed hex string or None
pub fn serialize_optional_uint8array_as_hex<S>(
buffer: &Option<Uint8Array>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match buffer {
Some(buf) => {
let hex_string = format!("0x{}", hex::encode(buf));
serializer.serialize_str(&hex_string)
}
None => serializer.serialize_none(),
}
}
/// Serialize a `BigInt` as a struct with `sign_bit` and `words` fields
pub fn serialize_bigint_as_struct<S>(value: &BigInt, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("BigInt", 2)?;
state.serialize_field("sign_bit", &value.sign_bit)?;
state.serialize_field("words", &value.words)?;
state.end()
}
/// Serialize an Option<BigInt> as a struct with `sign_bit` and `words` fields
/// or None
pub fn serialize_optional_bigint_as_struct<S>(
value: &Option<BigInt>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(val) => serialize_bigint_as_struct(val, serializer),
None => serializer.serialize_none(),
}
}

View File

@@ -0,0 +1,57 @@
pub mod artifact;
pub mod cheatcode_errors;
pub mod config;
pub mod factory;
pub mod l1;
#[cfg(feature = "op")]
pub mod op;
pub mod runner;
pub mod test_results;
use std::path::Path;
use edr_primitives::Bytes;
use edr_solidity::linker::{LinkOutput, Linker};
use edr_solidity_tests::{constants::LIBRARY_DEPLOYER, contracts::ContractsByArtifact};
use foundry_compilers::artifacts::Libraries;
use crate::solidity_tests::artifact::Artifact;
pub(crate) struct LinkingOutput {
pub libs_to_deploy: Vec<Bytes>,
pub known_contracts: ContractsByArtifact,
}
impl LinkingOutput {
pub fn link(project_root: &Path, artifacts: Vec<Artifact>) -> napi::Result<Self> {
let artifact_contracts = artifacts
.into_iter()
.map(|artifact| Ok((artifact.id.try_into()?, artifact.contract.try_into()?)))
.collect::<napi::Result<Vec<_>>>()?;
let linker = Linker::new(project_root, artifact_contracts);
let LinkOutput {
libraries,
libs_to_deploy,
} = linker
.link_with_nonce_or_address(
Libraries::default(),
LIBRARY_DEPLOYER,
0,
linker.contracts.keys(),
)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let linked_contracts = linker
.get_linked_artifacts(&libraries)
.map_err(|error| napi::Error::from_reason(error.to_string()))?;
let known_contracts = ContractsByArtifact::new(linked_contracts);
Ok(LinkingOutput {
libs_to_deploy,
known_contracts,
})
}
}

View File

@@ -0,0 +1,184 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use napi_derive::napi;
/// A compilation artifact.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct Artifact {
/// The identifier of the artifact.
pub id: ArtifactId,
/// The test contract.
pub contract: ContractData,
}
/// The identifier of a Solidity contract.
#[derive(Clone, Debug, serde::Serialize)]
#[napi(object)]
pub struct ArtifactId {
/// The name of the contract.
pub name: String,
/// Original source file path.
pub source: String,
/// The solc semver string.
pub solc_version: String,
}
impl From<edr_solidity::artifacts::ArtifactId> for ArtifactId {
fn from(value: edr_solidity::artifacts::ArtifactId) -> Self {
Self {
name: value.name,
source: value.source.to_string_lossy().to_string(),
solc_version: value.version.to_string(),
}
}
}
impl TryFrom<ArtifactId> for edr_solidity::artifacts::ArtifactId {
type Error = napi::Error;
fn try_from(value: ArtifactId) -> napi::Result<Self> {
Ok(edr_solidity::artifacts::ArtifactId {
name: value.name,
source: value.source.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid source path")
})?,
version: value.solc_version.parse().map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid solc semver string")
})?,
})
}
}
/// A test contract to execute.
#[derive(Clone, Debug)]
#[napi(object)]
pub struct ContractData {
/// Contract ABI as json string.
pub abi: String,
/// Contract creation code as hex string. It can be missing if the contract
/// is ABI only.
pub bytecode: Option<String>,
/// The link references of the deployment bytecode.
pub link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
/// Contract runtime code as hex string. It can be missing if the contract
/// is ABI only.
pub deployed_bytecode: Option<String>,
/// The link references of the deployed bytecode.
pub deployed_link_references: Option<HashMap<String, HashMap<String, Vec<LinkReference>>>>,
}
impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecode {
type Error = napi::Error;
fn try_from(contract: ContractData) -> napi::Result<Self> {
Ok(foundry_compilers::artifacts::CompactContractBytecode {
abi: Some(serde_json::from_str(&contract.abi).map_err(|_err| {
napi::Error::new(napi::Status::GenericFailure, "Invalid JSON ABI")
})?),
bytecode: contract
.bytecode
.map(|bytecode| {
let link_references =
convert_link_references(contract.link_references.unwrap_or_default());
let object = convert_bytecode(bytecode, !link_references.is_empty())?;
Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactBytecode {
object,
source_map: None,
link_references,
})
})
.transpose()?,
deployed_bytecode: contract
.deployed_bytecode
.map(|deployed_bytecode| {
let link_references = convert_link_references(
contract.deployed_link_references.unwrap_or_default(),
);
let object = convert_bytecode(deployed_bytecode, !link_references.is_empty())?;
let compact_bytecode = foundry_compilers::artifacts::CompactBytecode {
object,
source_map: None,
link_references,
};
Ok::<_, napi::Error>(foundry_compilers::artifacts::CompactDeployedBytecode {
bytecode: Some(compact_bytecode),
immutable_references: BTreeMap::default(),
})
})
.transpose()?,
})
}
}
impl TryFrom<ContractData> for foundry_compilers::artifacts::CompactContractBytecodeCow<'static> {
type Error = napi::Error;
fn try_from(contract: ContractData) -> napi::Result<Self> {
let c: foundry_compilers::artifacts::CompactContractBytecode = contract.try_into()?;
Ok(foundry_compilers::artifacts::CompactContractBytecodeCow {
abi: c.abi.map(Cow::Owned),
bytecode: c.bytecode.map(Cow::Owned),
deployed_bytecode: c.deployed_bytecode.map(Cow::Owned),
})
}
}
// The order of link references as supplied through the NAPI interface doesn't
// matter, but the order can matter downstream for deterministic address
// generation.
fn convert_link_references(
link_references: HashMap<String, HashMap<String, Vec<LinkReference>>>,
) -> BTreeMap<String, BTreeMap<String, Vec<foundry_compilers::artifacts::Offsets>>> {
link_references
.into_iter()
.map(|(file, libraries)| {
let lib_map = libraries
.into_iter()
.map(|(library_name, references)| {
let offsets = references.into_iter().map(Into::into).collect();
(library_name, offsets)
})
.collect();
(file, lib_map)
})
.collect()
}
fn convert_bytecode(
bytecode: String,
needs_linking: bool,
) -> napi::Result<foundry_compilers::artifacts::BytecodeObject> {
if needs_linking {
Ok(foundry_compilers::artifacts::BytecodeObject::Unlinked(
bytecode,
))
} else {
let bytes = bytecode.parse().map_err(|err| {
let message = format!("Hex decoding error while parsing bytecode: '{err}'. Maybe forgot to pass link references for a contract that needs linking?");
napi::Error::from_reason(message)
})?;
Ok(foundry_compilers::artifacts::BytecodeObject::Bytecode(
bytes,
))
}
}
#[derive(Clone, Debug)]
#[napi(object)]
pub struct LinkReference {
pub start: u32,
pub length: u32,
}
impl From<LinkReference> for foundry_compilers::artifacts::Offsets {
fn from(value: LinkReference) -> Self {
Self {
start: value.start,
length: value.length,
}
}
}

View File

@@ -0,0 +1,37 @@
use napi_derive::napi;
use serde::Serialize;
#[napi(string_enum)]
#[derive(Serialize)]
#[doc = "Error codes that can be returned by cheatcodes in Solidity tests."]
pub enum CheatcodeErrorCode {
#[doc = "The specified cheatcode is not supported."]
UnsupportedCheatcode,
#[doc = "The specified cheatcode is missing."]
MissingCheatcode,
}
#[napi(object)]
#[derive(Clone, Serialize)]
#[doc = "Error returned by a cheatcode in Solidity tests."]
pub struct CheatcodeErrorDetails {
#[doc = "The error code representing the type of cheatcode error."]
pub code: CheatcodeErrorCode,
#[doc = "The name of the cheatcode that caused the error."]
pub cheatcode: String,
}
impl From<edr_solidity::return_data::CheatcodeErrorCode> for CheatcodeErrorCode {
fn from(value: edr_solidity::return_data::CheatcodeErrorCode) -> Self {
match value {
edr_solidity::return_data::CheatcodeErrorCode::UnsupportedCheatcode => {
CheatcodeErrorCode::UnsupportedCheatcode
}
// __Invalid is generated by alloy_sol_types for invalid encoded values.
edr_solidity::return_data::CheatcodeErrorCode::MissingCheatcode
| edr_solidity::return_data::CheatcodeErrorCode::__Invalid => {
CheatcodeErrorCode::MissingCheatcode
}
}
}
}

View File

@@ -0,0 +1,984 @@
use std::{collections::HashMap, path::PathBuf};
use derive_more::Debug;
use edr_primitives::hex;
use edr_solidity_tests::{
executors::invariant::InvariantConfig,
fuzz::FuzzConfig,
inspectors::cheatcodes::{CheatsConfigOptions, ExecutionContextConfig},
TestFilterConfig,
};
use foundry_cheatcodes::{FsPermissions, RpcEndpointUrl, RpcEndpoints};
use napi::{
bindgen_prelude::{BigInt, Uint8Array},
tokio::runtime,
Either, Status,
};
use napi_derive::napi;
use crate::{
account::AccountOverride,
cast::TryCast,
config::ObservabilityConfig,
serde::{
serialize_optional_bigint_as_struct, serialize_optional_uint8array_as_hex,
serialize_uint8array_as_hex,
},
solidity_tests::artifact::ArtifactId,
};
/// Solidity test runner configuration arguments exposed through the ffi.
/// Docs based on <https://book.getfoundry.sh/reference/config/testing>.
#[napi(object)]
#[derive(Debug, serde::Serialize)]
pub struct SolidityTestRunnerConfigArgs {
/// The absolute path to the project root directory.
/// Relative paths in cheat codes are resolved against this path.
pub project_root: String,
/// Configures the permissions of cheat codes that access the file system.
pub fs_permissions: Option<Vec<PathPermission>>,
/// Address labels for traces. Defaults to none.
pub labels: Option<Vec<AddressLabel>>,
/// Whether to enable isolation of calls. In isolation mode all top-level
/// calls are executed as a separate transaction in a separate EVM
/// context, enabling more precise gas accounting and transaction state
/// changes.
/// Defaults to false.
pub isolate: Option<bool>,
/// Whether or not to enable the ffi cheatcode.
/// Warning: Enabling this cheatcode has security implications, as it allows
/// tests to execute arbitrary programs on your computer.
/// Defaults to false.
pub ffi: Option<bool>,
/// Allow expecting reverts with `expectRevert` at the same callstack depth
/// as the test. Defaults to false.
pub allow_internal_expect_revert: Option<bool>,
/// The value of `msg.sender` in tests as hex string.
/// Defaults to `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`.
#[debug("{:?}", sender.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub sender: Option<Uint8Array>,
/// The value of `tx.origin` in tests as hex string.
/// Defaults to `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`.
#[debug("{:?}", tx_origin.as_ref().map(hex::encode))]
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
pub tx_origin: Option<Uint8Array>,
/// The initial balance of the sender in tests.
/// Defaults to `0xffffffffffffffffffffffff`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub initial_balance: Option<BigInt>,
/// The value of `block.number` in tests.
/// Defaults to `1`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_number: Option<BigInt>,
/// The value of the `chainid` opcode in tests.
/// Defaults to `31337`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub chain_id: Option<BigInt>,
/// The hardfork to use for EVM execution.
pub hardfork: String,
/// The gas limit for each test case.
/// Defaults to `9_223_372_036_854_775_807` (`i64::MAX`).
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub gas_limit: Option<BigInt>,
/// The price of gas (in wei) in tests.
/// Defaults to `0`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub gas_price: Option<BigInt>,
/// The base fee per gas (in wei) in tests.
/// Defaults to `0`.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_base_fee_per_gas: Option<BigInt>,
/// The value of `block.coinbase` in tests.
/// Defaults to `0x0000000000000000000000000000000000000000`.
#[serde(serialize_with = "serialize_optional_uint8array_as_hex")]
#[debug("{:?}", block_coinbase.as_ref().map(hex::encode))]
pub block_coinbase: Option<Uint8Array>,
/// The value of `block.timestamp` in tests.
/// Defaults to 1.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_timestamp: Option<BigInt>,
/// The value of `block.difficulty` in tests.
/// Defaults to 0.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_difficulty: Option<BigInt>,
/// The `block.gaslimit` value during EVM execution.
/// Defaults to none.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub block_gas_limit: Option<BigInt>,
/// Whether to disable the block gas limit.
/// Defaults to false.
pub disable_block_gas_limit: Option<bool>,
/// Whether to enable the EIP-7825 (Osaka) transaction gas limit cap.
/// Defaults to false.
pub enable_tx_gas_limit_cap: Option<bool>,
/// The memory limit of the EVM in bytes.
/// Defaults to `33_554_432` (2^25 = 32MiB).
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub memory_limit: Option<BigInt>,
/// The predeploys applied in local mode. Defaults to no predeploys.
/// These should match the predeploys of the network in fork mode, so they
/// aren't set in fork mode.
/// The code must be set and non-empty. The nonce and the balance default to
/// zero and storage defaults to empty.
pub local_predeploys: Option<Vec<AccountOverride>>,
/// If set, all tests are run in fork mode using this url or remote name.
/// Defaults to none.
pub eth_rpc_url: Option<String>,
/// Pins the block number for the global state fork.
#[serde(serialize_with = "serialize_optional_bigint_as_struct")]
pub fork_block_number: Option<BigInt>,
/// Map of RPC endpoints from chain name to RPC urls for fork cheat codes,
/// e.g. `{ "optimism": "https://optimism.alchemyapi.io/v2/..." }`
pub rpc_endpoints: Option<HashMap<String, String>>,
/// Optional RPC cache path. If this is none, then no RPC calls will be
/// cached, otherwise data is cached to `<rpc_cache_path>/<chain
/// id>/<block number>`. Caching can be disabled for specific chains
/// with `rpc_storage_caching`.
pub rpc_cache_path: Option<String>,
/// What RPC endpoints are cached. Defaults to all.
pub rpc_storage_caching: Option<StorageCachingConfig>,
/// The number of seconds to wait before `vm.prompt` reverts with a timeout.
/// Defaults to 120.
pub prompt_timeout: Option<u32>,
/// Fuzz testing configuration.
pub fuzz: Option<FuzzConfigArgs>,
/// Invariant testing configuration.
/// If an invariant config setting is not set, but a corresponding fuzz
/// config value is set, then the fuzz config value will be used.
pub invariant: Option<InvariantConfigArgs>,
/// Whether to collect stack traces.
pub collect_stack_traces: Option<CollectStackTraces>,
/// Controls which test results should include execution traces. Defaults to
/// None.
pub include_traces: Option<IncludeTraces>,
/// The configuration for the Solidity test runner's observability
#[debug(skip)]
#[serde(skip)]
pub observability: Option<ObservabilityConfig>,
/// A regex pattern to filter tests. If provided, only test methods that
/// match the pattern will be executed and reported as a test result.
pub test_pattern: Option<String>,
/// Controls whether to generate a gas report after running the tests.
/// Enabling this also enables collection of all traces and EVM isolation
/// mode.
/// Defaults to false.
pub generate_gas_report: Option<bool>,
/// Test function level config overrides.
/// Defaults to none.
pub test_function_overrides: Option<Vec<TestFunctionOverride>>,
}
impl SolidityTestRunnerConfigArgs {
/// Resolves the instance, converting it to a
/// [`edr_napi_core::solidity::config::TestRunnerConfig`].
pub fn resolve(
self,
env: &napi::Env,
runtime: runtime::Handle,
) -> napi::Result<edr_napi_core::solidity::config::TestRunnerConfig> {
let SolidityTestRunnerConfigArgs {
project_root,
fs_permissions,
labels,
isolate,
ffi,
allow_internal_expect_revert,
sender,
tx_origin,
initial_balance,
block_number,
chain_id,
hardfork,
gas_limit,
gas_price,
block_base_fee_per_gas,
block_coinbase,
block_timestamp,
block_difficulty,
block_gas_limit,
disable_block_gas_limit,
enable_tx_gas_limit_cap,
memory_limit,
local_predeploys,
eth_rpc_url,
rpc_cache_path,
fork_block_number,
rpc_endpoints,
rpc_storage_caching,
prompt_timeout,
fuzz,
invariant,
collect_stack_traces,
include_traces,
observability,
test_pattern,
generate_gas_report,
test_function_overrides,
} = self;
let test_pattern = TestFilterConfig {
test_pattern: test_pattern
.as_ref()
.map(|p| {
p.parse()
.map_err(|error| napi::Error::new(Status::InvalidArg, error))
})
.transpose()?,
};
let local_predeploys = local_predeploys
.map(|local_predeploys| {
local_predeploys
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()
})
.transpose()?;
let invariant: InvariantConfig = fuzz
.as_ref()
.map(|f| invariant.clone().unwrap_or_default().defaults_from_fuzz(f))
.or(invariant)
.map(TryFrom::try_from)
.transpose()?
.unwrap_or_default();
let fuzz: FuzzConfig = fuzz.map(TryFrom::try_from).transpose()?.unwrap_or_default();
let cheatcode = CheatsConfigOptions {
// TODO https://github.com/NomicFoundation/edr/issues/657
// If gas reporting or coverage is supported, take that into account here.
execution_context: ExecutionContextConfig::Test,
rpc_endpoints: rpc_endpoints
.map(|endpoints| {
RpcEndpoints::new(
endpoints
.into_iter()
.map(|(chain, url)| (chain, RpcEndpointUrl::new(url))),
)
})
.unwrap_or_default(),
rpc_cache_path: rpc_cache_path.map(PathBuf::from),
rpc_storage_caching: rpc_storage_caching
.map(TryFrom::try_from)
.transpose()?
.unwrap_or_default(),
fs_permissions: FsPermissions::new(
fs_permissions
.unwrap_or_default()
.into_iter()
.map(Into::into),
),
prompt_timeout: prompt_timeout.map_or(120, Into::into),
labels: labels
.unwrap_or_default()
.into_iter()
.map(|AddressLabel { address, label }| Ok((address.try_cast()?, label)))
.collect::<Result<_, napi::Error>>()?,
seed: fuzz.seed,
allow_internal_expect_revert: allow_internal_expect_revert.unwrap_or(false),
functions_internal_expect_revert: test_function_overrides
.as_ref()
.map(|overrides| {
overrides
.iter()
.filter(|override_item| {
override_item
.config
.allow_internal_expect_revert
.unwrap_or_default()
})
.map(|override_item| override_item.identifier.clone().try_into())
.collect::<Result<_, _>>()
})
.transpose()?
.unwrap_or_default(),
};
let on_collected_coverage_fn = observability.map_or_else(
|| Ok(None),
|observability| {
observability
.resolve(env, runtime)
.map(|config| config.on_collected_coverage_fn)
},
)?;
let config = edr_napi_core::solidity::config::TestRunnerConfig {
project_root: project_root.into(),
include_traces: include_traces.unwrap_or_default().into(),
isolate,
ffi,
sender: sender.map(TryCast::try_cast).transpose()?,
tx_origin: tx_origin.map(TryCast::try_cast).transpose()?,
initial_balance: initial_balance.map(TryCast::try_cast).transpose()?,
block_number: block_number.map(TryCast::try_cast).transpose()?,
chain_id: chain_id.map(TryCast::try_cast).transpose()?,
hardfork,
gas_limit: gas_limit.map(TryCast::try_cast).transpose()?,
gas_price: gas_price.map(TryCast::try_cast).transpose()?,
block_base_fee_per_gas: block_base_fee_per_gas.map(TryCast::try_cast).transpose()?,
block_coinbase: block_coinbase.map(TryCast::try_cast).transpose()?,
block_timestamp: block_timestamp.map(TryCast::try_cast).transpose()?,
block_difficulty: block_difficulty.map(TryCast::try_cast).transpose()?,
block_gas_limit: block_gas_limit.map(TryCast::try_cast).transpose()?,
disable_block_gas_limit,
enable_tx_gas_limit_cap,
memory_limit: memory_limit.map(TryCast::try_cast).transpose()?,
local_predeploys,
fork_url: eth_rpc_url,
fork_block_number: fork_block_number.map(TryCast::try_cast).transpose()?,
cheatcode,
fuzz,
invariant,
collect_stack_traces: collect_stack_traces.map_or(
edr_solidity_tests::CollectStackTraces::OnFailure,
edr_solidity_tests::CollectStackTraces::from,
),
on_collected_coverage_fn,
test_pattern,
generate_gas_report,
test_function_overrides: test_function_overrides
.map(|overrides| {
overrides
.into_iter()
.map(|override_item| {
Ok::<_, napi::Error>((
override_item.identifier.try_into()?,
override_item.config.into(),
))
})
.collect::<Result<HashMap<_, _>, _>>()
})
.transpose()?,
};
Ok(config)
}
}
/// Fuzz testing configuration
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct FuzzConfigArgs {
/// Path where fuzz failures are recorded and replayed if set.
pub failure_persist_dir: Option<String>,
/// Name of the file to record fuzz failures, defaults to `failures`.
pub failure_persist_file: Option<String>,
/// The amount of fuzz runs to perform for each fuzz test case. Higher
/// values gives more confidence in results at the cost of testing
/// speed.
/// Defaults to 256.
pub runs: Option<u32>,
/// The maximum number of combined inputs that may be rejected before the
/// test as a whole aborts. “Global” filters apply to the whole test
/// case. If the test case is rejected, the whole thing is regenerated.
/// Defaults to 65536.
pub max_test_rejects: Option<u32>,
/// Hexadecimal string.
/// Optional seed for the fuzzing RNG algorithm.
/// Defaults to None.
pub seed: Option<String>,
/// Integer between 0 and 100.
/// The weight of the dictionary. A higher dictionary weight will bias the
/// fuzz inputs towards “interesting” values, e.g. boundary values like
/// type(uint256).max or contract addresses from your environment.
/// Defaults to 40.
pub dictionary_weight: Option<u32>,
/// The flag indicating whether to include values from storage.
/// Defaults to true.
pub include_storage: Option<bool>,
/// The flag indicating whether to include push bytes values.
/// Defaults to true.
pub include_push_bytes: Option<bool>,
/// Optional timeout (in seconds) for each property test.
/// Defaults to none (no timeout).
pub timeout: Option<u32>,
}
impl TryFrom<FuzzConfigArgs> for FuzzConfig {
type Error = napi::Error;
fn try_from(value: FuzzConfigArgs) -> Result<Self, Self::Error> {
let FuzzConfigArgs {
failure_persist_dir,
failure_persist_file,
runs,
max_test_rejects,
seed,
dictionary_weight,
include_storage,
include_push_bytes,
timeout,
} = value;
let failure_persist_dir = failure_persist_dir.map(PathBuf::from);
let failure_persist_file = failure_persist_file.unwrap_or_else(|| "failures".to_string());
let seed = seed
.map(|s| {
s.parse().map_err(|_err| {
napi::Error::new(Status::InvalidArg, format!("Invalid seed value: {s}"))
})
})
.transpose()?;
let mut fuzz = FuzzConfig {
seed,
failure_persist_dir,
failure_persist_file,
// TODO https://github.com/NomicFoundation/edr/issues/657
gas_report_samples: 0,
timeout,
..FuzzConfig::default()
};
if let Some(runs) = runs {
fuzz.runs = runs;
}
if let Some(max_test_rejects) = max_test_rejects {
fuzz.max_test_rejects = max_test_rejects;
}
if let Some(dictionary_weight) = dictionary_weight {
fuzz.dictionary.dictionary_weight = dictionary_weight;
}
if let Some(include_storage) = include_storage {
fuzz.dictionary.include_storage = include_storage;
}
if let Some(include_push_bytes) = include_push_bytes {
fuzz.dictionary.include_push_bytes = include_push_bytes;
}
Ok(fuzz)
}
}
impl SolidityTestRunnerConfigArgs {
pub fn try_get_test_filter(&self) -> napi::Result<TestFilterConfig> {
let test_pattern = self
.test_pattern
.as_ref()
.map(|p| {
p.parse()
.map_err(|e| napi::Error::new(Status::InvalidArg, e))
})
.transpose()?;
Ok(TestFilterConfig { test_pattern })
}
}
/// Invariant testing configuration.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct InvariantConfigArgs {
/// Path where invariant failures are recorded and replayed if set.
pub failure_persist_dir: Option<String>,
/// The number of runs that must execute for each invariant test group.
/// Defaults to 256.
pub runs: Option<u32>,
/// The number of calls executed to attempt to break invariants in one run.
/// Defaults to 500.
pub depth: Option<u32>,
/// Fails the invariant fuzzing if a revert occurs.
/// Defaults to false.
pub fail_on_revert: Option<bool>,
/// Overrides unsafe external calls when running invariant tests, useful for
/// e.g. performing reentrancy checks.
/// Defaults to false.
pub call_override: Option<bool>,
/// Integer between 0 and 100.
/// The weight of the dictionary. A higher dictionary weight will bias the
/// fuzz inputs towards “interesting” values, e.g. boundary values like
/// type(uint256).max or contract addresses from your environment.
/// Defaults to 40.
pub dictionary_weight: Option<u32>,
/// The flag indicating whether to include values from storage.
/// Defaults to true.
pub include_storage: Option<bool>,
/// The flag indicating whether to include push bytes values.
/// Defaults to true.
pub include_push_bytes: Option<bool>,
/// The maximum number of attempts to shrink a failed the sequence. Shrink
/// process is disabled if set to 0.
/// Defaults to 5000.
pub shrink_run_limit: Option<u32>,
/// The maximum number of rejects via `vm.assume` which can be encountered
/// during a single invariant run.
/// Defaults to 65536.
pub max_assume_rejects: Option<u32>,
/// Optional timeout (in seconds) for each invariant test.
/// Defaults to none (no timeout).
pub timeout: Option<u32>,
}
impl InvariantConfigArgs {
/// Fill in fields from the fuzz config if they are not set.
fn defaults_from_fuzz(mut self, fuzz: &FuzzConfigArgs) -> Self {
let FuzzConfigArgs {
failure_persist_dir,
runs,
dictionary_weight,
include_storage,
include_push_bytes,
// These aren't used in the invariant config.
failure_persist_file: _,
max_test_rejects: _,
seed: _,
timeout,
} = fuzz;
if self.failure_persist_dir.is_none() {
self.failure_persist_dir.clone_from(failure_persist_dir);
}
if self.runs.is_none() {
self.runs = *runs;
}
if self.dictionary_weight.is_none() {
self.dictionary_weight = *dictionary_weight;
}
if self.include_storage.is_none() {
self.include_storage = *include_storage;
}
if self.include_push_bytes.is_none() {
self.include_push_bytes = *include_push_bytes;
}
if self.timeout.is_none() {
self.timeout = *timeout;
}
self
}
}
impl From<InvariantConfigArgs> for InvariantConfig {
fn from(value: InvariantConfigArgs) -> Self {
let InvariantConfigArgs {
failure_persist_dir,
runs,
depth,
fail_on_revert,
call_override,
dictionary_weight,
include_storage,
include_push_bytes,
shrink_run_limit,
max_assume_rejects,
timeout,
} = value;
let failure_persist_dir = failure_persist_dir.map(PathBuf::from);
let mut invariant = InvariantConfig {
failure_persist_dir,
// TODO https://github.com/NomicFoundation/edr/issues/657
gas_report_samples: 0,
timeout,
..InvariantConfig::default()
};
if let Some(runs) = runs {
invariant.runs = runs;
}
if let Some(depth) = depth {
invariant.depth = depth;
}
if let Some(fail_on_revert) = fail_on_revert {
invariant.fail_on_revert = fail_on_revert;
}
if let Some(call_override) = call_override {
invariant.call_override = call_override;
}
if let Some(dictionary_weight) = dictionary_weight {
invariant.dictionary.dictionary_weight = dictionary_weight;
}
if let Some(include_storage) = include_storage {
invariant.dictionary.include_storage = include_storage;
}
if let Some(include_push_bytes) = include_push_bytes {
invariant.dictionary.include_push_bytes = include_push_bytes;
}
if let Some(shrink_run_limit) = shrink_run_limit {
invariant.shrink_run_limit = shrink_run_limit;
}
if let Some(max_assume_rejects) = max_assume_rejects {
invariant.max_assume_rejects = max_assume_rejects;
}
invariant
}
}
/// Settings to configure caching of remote RPC endpoints.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct StorageCachingConfig {
/// Chains to cache. Either all or none or a list of chain names, e.g.
/// ["optimism", "mainnet"].
pub chains: Either<CachedChains, Vec<String>>,
/// Endpoints to cache. Either all or remote or a regex.
pub endpoints: Either<CachedEndpoints, String>,
}
impl Default for StorageCachingConfig {
fn default() -> Self {
Self {
chains: Either::A(CachedChains::default()),
endpoints: Either::A(CachedEndpoints::default()),
}
}
}
impl TryFrom<StorageCachingConfig> for foundry_cheatcodes::StorageCachingConfig {
type Error = napi::Error;
fn try_from(value: StorageCachingConfig) -> Result<Self, Self::Error> {
let chains = match value.chains {
Either::A(chains) => chains.into(),
Either::B(chains) => {
let chains = chains
.into_iter()
.map(|c| {
c.parse()
.map_err(|c| napi::Error::new(Status::InvalidArg, c))
})
.collect::<Result<_, _>>()?;
foundry_cheatcodes::CachedChains::Chains(chains)
}
};
let endpoints = match value.endpoints {
Either::A(endpoints) => endpoints.into(),
Either::B(regex) => {
let regex = regex.parse().map_err(|_err| {
napi::Error::new(Status::InvalidArg, format!("Invalid regex: {regex}"))
})?;
foundry_cheatcodes::CachedEndpoints::Pattern(regex)
}
};
Ok(Self { chains, endpoints })
}
}
/// What chains to cache
#[napi]
#[derive(Debug, Default, serde::Serialize)]
pub enum CachedChains {
/// Cache all chains
#[default]
All,
/// Don't cache anything
None,
}
impl From<CachedChains> for foundry_cheatcodes::CachedChains {
fn from(value: CachedChains) -> Self {
match value {
CachedChains::All => foundry_cheatcodes::CachedChains::All,
CachedChains::None => foundry_cheatcodes::CachedChains::None,
}
}
}
/// What endpoints to enable caching for
#[napi]
#[derive(Debug, Default, serde::Serialize)]
pub enum CachedEndpoints {
/// Cache all endpoints
#[default]
All,
/// Only cache non-local host endpoints
Remote,
}
impl From<CachedEndpoints> for foundry_cheatcodes::CachedEndpoints {
fn from(value: CachedEndpoints) -> Self {
match value {
CachedEndpoints::All => foundry_cheatcodes::CachedEndpoints::All,
CachedEndpoints::Remote => foundry_cheatcodes::CachedEndpoints::Remote,
}
}
}
/// Represents an access permission to a single path
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct PathPermission {
/// Permission level to access the `path`
pub access: FsAccessPermission,
/// The targeted path guarded by the permission
pub path: String,
}
impl From<PathPermission> for foundry_cheatcodes::PathPermission {
fn from(value: PathPermission) -> Self {
let PathPermission { access, path } = value;
Self {
access: access.into(),
path: path.into(),
}
}
}
/**
* Determines the level of file system access for the given path.
*
* Exact path matching is used for file permissions. Prefix matching is used
* for directory permissions.
*
* Giving write access to configuration files, source files or executables
* in a project is considered dangerous, because it can be used by malicious
* Solidity dependencies to escape the EVM sandbox. It is therefore
* recommended to give write access to specific safe files only. If write
* access to a directory is needed, please make sure that it doesn't contain
* configuration files, source files or executables neither in the top level
* directory, nor in any subdirectories.
*/
#[napi]
#[derive(Debug, serde::Serialize)]
pub enum FsAccessPermission {
/// Allows reading and writing the file
ReadWriteFile,
/// Only allows reading the file
ReadFile,
/// Only allows writing the file
WriteFile,
/// Allows reading and writing all files in the directory and its
/// subdirectories
DangerouslyReadWriteDirectory,
/// Allows reading all files in the directory and its subdirectories
ReadDirectory,
/// Allows writing all files in the directory and its subdirectories
DangerouslyWriteDirectory,
}
impl From<FsAccessPermission> for foundry_cheatcodes::FsAccessPermission {
fn from(value: FsAccessPermission) -> Self {
match value {
FsAccessPermission::ReadWriteFile => {
foundry_cheatcodes::FsAccessPermission::ReadWriteFile
}
FsAccessPermission::ReadFile => foundry_cheatcodes::FsAccessPermission::ReadFile,
FsAccessPermission::WriteFile => foundry_cheatcodes::FsAccessPermission::WriteFile,
FsAccessPermission::DangerouslyReadWriteDirectory => {
foundry_cheatcodes::FsAccessPermission::DangerouslyReadWriteDirectory
}
FsAccessPermission::ReadDirectory => {
foundry_cheatcodes::FsAccessPermission::ReadDirectory
}
FsAccessPermission::DangerouslyWriteDirectory => {
foundry_cheatcodes::FsAccessPermission::DangerouslyWriteDirectory
}
}
}
}
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct AddressLabel {
/// The address to label
#[serde(serialize_with = "serialize_uint8array_as_hex")]
#[debug("{}", hex::encode(address))]
pub address: Uint8Array,
/// The label to assign to the address
pub label: String,
}
/// A type that controls when stack traces are collected.
#[napi]
#[derive(Debug, serde::Serialize)]
pub enum CollectStackTraces {
/// Always collects stack traces, adding performance overhead.
Always,
/// Only collects stack traces upon failure, re-executing the test. This
/// minimizes performance overhead.
///
/// Not all tests can be re-executed since certain cheatcodes contain
/// non-deterministic side-effects.
OnFailure,
}
impl From<CollectStackTraces> for edr_solidity_tests::CollectStackTraces {
fn from(value: CollectStackTraces) -> Self {
match value {
CollectStackTraces::Always => edr_solidity_tests::CollectStackTraces::Always,
CollectStackTraces::OnFailure => edr_solidity_tests::CollectStackTraces::OnFailure,
}
}
}
/// Configuration for [`SolidityTestRunnerConfigArgs::include_traces`] that
/// controls execution trace decoding and inclusion in test results.
#[napi]
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
pub enum IncludeTraces {
/// No traces will be included in any test result.
#[default]
None,
/// Traces will be included only on the results of failed tests.
Failing,
/// Traces will be included in all test results.
All,
}
impl From<IncludeTraces> for edr_solidity_tests::IncludeTraces {
fn from(value: IncludeTraces) -> Self {
match value {
IncludeTraces::None => edr_solidity_tests::IncludeTraces::None,
IncludeTraces::Failing => edr_solidity_tests::IncludeTraces::Failing,
IncludeTraces::All => edr_solidity_tests::IncludeTraces::All,
}
}
}
impl From<edr_solidity_tests::IncludeTraces> for IncludeTraces {
fn from(value: edr_solidity_tests::IncludeTraces) -> Self {
match value {
edr_solidity_tests::IncludeTraces::None => IncludeTraces::None,
edr_solidity_tests::IncludeTraces::Failing => IncludeTraces::Failing,
edr_solidity_tests::IncludeTraces::All => IncludeTraces::All,
}
}
}
/// Test function level config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct TestFunctionConfigOverride {
/// Allow expecting reverts with `expectRevert` at the same callstack depth
/// as the test.
pub allow_internal_expect_revert: Option<bool>,
/// Configuration override for fuzz testing.
pub fuzz: Option<FuzzConfigOverride>,
/// Configuration override for invariant testing.
pub invariant: Option<InvariantConfigOverride>,
}
impl From<TestFunctionConfigOverride> for edr_solidity_tests::TestFunctionConfigOverride {
fn from(value: TestFunctionConfigOverride) -> Self {
Self {
allow_internal_expect_revert: value.allow_internal_expect_revert,
fuzz: value.fuzz.map(Into::into),
invariant: value.invariant.map(Into::into),
}
}
}
/// Test function override configuration.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct TestFunctionOverride {
/// The test function identifier.
pub identifier: TestFunctionIdentifier,
/// The configuration override.
pub config: TestFunctionConfigOverride,
}
/// Test function identifier.
#[napi(object)]
#[derive(Clone, Debug, serde::Serialize)]
pub struct TestFunctionIdentifier {
/// The contract artifact id.
pub contract_artifact: ArtifactId,
/// The function selector as hex string.
pub function_selector: String,
}
impl TryFrom<TestFunctionIdentifier> for foundry_cheatcodes::TestFunctionIdentifier {
type Error = napi::Error;
fn try_from(value: TestFunctionIdentifier) -> napi::Result<Self> {
Ok(foundry_cheatcodes::TestFunctionIdentifier {
contract_artifact: value.contract_artifact.try_into()?,
function_selector: value.function_selector,
})
}
}
/// Timeout configuration.
/// Note: This wrapper is needed to avoid ambiguity with NAPI conversion.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct TimeoutConfig {
/// Optional timeout (in seconds).
pub time: Option<u32>,
}
impl From<TimeoutConfig> for edr_solidity_tests::TimeoutConfig {
fn from(value: TimeoutConfig) -> Self {
Self { time: value.time }
}
}
/// Test function or test contract level fuzz config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct FuzzConfigOverride {
/// The number of test cases that must execute for each property test.
pub runs: Option<u32>,
/// The maximum number of test case rejections allowed by proptest, to be
/// encountered during usage of `vm.assume` cheatcode. This will be used
/// to set the `max_global_rejects` value in proptest test runner config.
/// `max_local_rejects` option isn't exposed here since we're not using
/// `prop_filter`.
pub max_test_rejects: Option<u32>,
/// show `console.log` in fuzz test, defaults to `false`.
pub show_logs: Option<bool>,
/// Optional timeout (in seconds) for each property test.
pub timeout: Option<TimeoutConfig>,
}
impl From<FuzzConfigOverride> for edr_solidity_tests::FuzzConfigOverride {
fn from(value: FuzzConfigOverride) -> Self {
Self {
runs: value.runs,
max_test_rejects: value.max_test_rejects,
show_logs: value.show_logs,
timeout: value.timeout.map(Into::into),
}
}
}
/// Test function or test contract level invariant config override.
#[napi(object)]
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct InvariantConfigOverride {
/// The number of runs that must execute for each invariant test group.
pub runs: Option<u32>,
/// The number of calls executed to attempt to break invariants in one run.
pub depth: Option<u32>,
/// Fails the invariant fuzzing if a revert occurs.
pub fail_on_revert: Option<bool>,
/// Allows overriding an unsafe external call when running invariant tests.
/// eg. reentrancy checks
pub call_override: Option<bool>,
/// Optional timeout (in seconds) for each invariant test.
pub timeout: Option<TimeoutConfig>,
}
impl From<InvariantConfigOverride> for edr_solidity_tests::InvariantConfigOverride {
fn from(value: InvariantConfigOverride) -> Self {
Self {
runs: value.runs,
depth: value.depth,
fail_on_revert: value.fail_on_revert,
call_override: value.call_override,
timeout: value.timeout.map(Into::into),
}
}
}

View File

@@ -0,0 +1,22 @@
use std::sync::Arc;
use edr_napi_core::solidity::SyncTestRunnerFactory;
use napi_derive::napi;
#[napi]
pub struct SolidityTestRunnerFactory {
inner: Arc<dyn SyncTestRunnerFactory>,
}
impl SolidityTestRunnerFactory {
/// Returns a reference to the inner test runner factory.
pub fn as_inner(&self) -> &Arc<dyn SyncTestRunnerFactory> {
&self.inner
}
}
impl From<Arc<dyn SyncTestRunnerFactory>> for SolidityTestRunnerFactory {
fn from(inner: Arc<dyn SyncTestRunnerFactory>) -> Self {
Self { inner }
}
}

View File

@@ -0,0 +1,72 @@
use std::{collections::BTreeMap, sync::Arc};
use edr_napi_core::solidity::{
config::{TestRunnerConfig, TracingConfigWithBuffers},
SyncTestRunner, SyncTestRunnerFactory,
};
use edr_primitives::Bytes;
use edr_solidity::artifacts::ArtifactId;
use edr_solidity_tests::{
contracts::ContractsByArtifact,
decode::RevertDecoder,
evm_context::L1EvmBuilder,
multi_runner::TestContract,
revm::context::{BlockEnv, TxEnv},
MultiContractRunner,
};
use napi::tokio;
use napi_derive::napi;
use crate::solidity_tests::{factory::SolidityTestRunnerFactory, runner::LazyContractDecoder};
struct L1TestRunnerFactory;
impl SyncTestRunnerFactory for L1TestRunnerFactory {
fn create_test_runner(
&self,
runtime: tokio::runtime::Handle,
config: TestRunnerConfig,
contracts: BTreeMap<ArtifactId, TestContract>,
known_contracts: ContractsByArtifact,
libs_to_deploy: Vec<Bytes>,
revert_decoder: RevertDecoder,
tracing_config: TracingConfigWithBuffers,
) -> napi::Result<Box<dyn SyncTestRunner>> {
let contract_decoder = LazyContractDecoder::new(tracing_config);
let runner = tokio::task::block_in_place(|| {
runtime
.block_on(MultiContractRunner::<
BlockEnv,
(),
L1EvmBuilder,
edr_chain_l1::HaltReason,
edr_chain_l1::Hardfork,
_,
edr_chain_l1::InvalidTransaction,
TxEnv,
>::new(
config.try_into()?,
contracts,
known_contracts,
libs_to_deploy,
contract_decoder,
revert_decoder,
))
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create multi contract runner: {err}"),
)
})
})?;
Ok(Box::new(runner))
}
}
#[napi(catch_unwind)]
pub fn l1_solidity_test_runner_factory() -> SolidityTestRunnerFactory {
let factory: Arc<dyn SyncTestRunnerFactory> = Arc::new(L1TestRunnerFactory);
factory.into()
}

View File

@@ -0,0 +1,72 @@
use std::{collections::BTreeMap, sync::Arc};
use edr_napi_core::solidity::{
config::{TestRunnerConfig, TracingConfigWithBuffers},
SyncTestRunner, SyncTestRunnerFactory,
};
use edr_op::{solidity_tests::OpEvmBuilder, transaction::OpTxEnv};
use edr_primitives::Bytes;
use edr_solidity::artifacts::ArtifactId;
use edr_solidity_tests::{
contracts::ContractsByArtifact,
decode::RevertDecoder,
multi_runner::TestContract,
revm::context::{BlockEnv, TxEnv},
MultiContractRunner,
};
use napi::tokio;
use napi_derive::napi;
use crate::solidity_tests::{factory::SolidityTestRunnerFactory, runner::LazyContractDecoder};
struct OpTestRunnerFactory;
impl SyncTestRunnerFactory for OpTestRunnerFactory {
fn create_test_runner(
&self,
runtime: tokio::runtime::Handle,
config: TestRunnerConfig,
contracts: BTreeMap<ArtifactId, TestContract>,
known_contracts: ContractsByArtifact,
libs_to_deploy: Vec<Bytes>,
revert_decoder: RevertDecoder,
tracing_config: TracingConfigWithBuffers,
) -> napi::Result<Box<dyn SyncTestRunner>> {
let contract_decoder = LazyContractDecoder::new(tracing_config);
let runner = tokio::task::block_in_place(|| {
runtime
.block_on(MultiContractRunner::<
BlockEnv,
_,
OpEvmBuilder,
edr_op::HaltReason,
edr_op::Hardfork,
_,
edr_op::InvalidTransaction,
OpTxEnv<TxEnv>,
>::new(
config.try_into()?,
contracts,
known_contracts,
libs_to_deploy,
contract_decoder,
revert_decoder,
))
.map_err(|err| {
napi::Error::new(
napi::Status::GenericFailure,
format!("Failed to create multi contract runner: {err}"),
)
})
})?;
Ok(Box::new(runner))
}
}
#[napi(catch_unwind)]
pub fn op_solidity_test_runner_factory() -> SolidityTestRunnerFactory {
let factory: Arc<dyn SyncTestRunnerFactory> = Arc::new(OpTestRunnerFactory);
factory.into()
}

View File

@@ -0,0 +1,51 @@
use std::sync::{Mutex, OnceLock};
use edr_chain_spec::HaltReasonTrait;
use edr_napi_core::solidity::config::TracingConfigWithBuffers;
use edr_solidity::{
artifacts::BuildInfoConfigWithBuffers,
contract_decoder::{ContractDecoder, ContractDecoderError, NestedTraceDecoder},
nested_trace::NestedTrace,
};
/// Only parses the tracing config which is very expensive if the contract
/// decoder is used.
#[derive(Debug)]
pub(crate) struct LazyContractDecoder {
// We need the `Mutex`, because `Uint8Array` is not `Sync`
tracing_config: Mutex<TracingConfigWithBuffers>,
// Storing the result so that we can propagate the error
contract_decoder: OnceLock<Result<ContractDecoder, ContractDecoderError>>,
}
impl LazyContractDecoder {
pub fn new(tracing_config: TracingConfigWithBuffers) -> Self {
Self {
tracing_config: Mutex::new(tracing_config),
contract_decoder: OnceLock::new(),
}
}
}
impl<HaltReasonT: HaltReasonTrait> NestedTraceDecoder<HaltReasonT> for LazyContractDecoder {
fn try_to_decode_nested_trace(
&self,
nested_trace: NestedTrace<HaltReasonT>,
) -> Result<NestedTrace<HaltReasonT>, ContractDecoderError> {
self.contract_decoder
.get_or_init(|| {
let tracing_config = self
.tracing_config
.lock()
.expect("Can't get poisoned, because only called once");
edr_solidity::artifacts::BuildInfoConfig::parse_from_buffers(
BuildInfoConfigWithBuffers::from(&*tracing_config),
)
.map_err(|err| ContractDecoderError::Initialization(err.to_string()))
.and_then(|config| ContractDecoder::new(&config))
})
.as_ref()
.map_err(Clone::clone)
.and_then(|decoder| decoder.try_to_decode_nested_trace(nested_trace))
}
}

View File

@@ -0,0 +1,790 @@
use std::{
borrow::Cow,
convert::Infallible,
fmt::{Debug, Formatter},
sync::Arc,
};
use edr_solidity_tests::{
constants::CHEATCODE_ADDRESS,
executors::stack_trace::StackTraceResult,
traces::{self, CallTraceArena, SparsedTraceArena},
};
use napi::{
bindgen_prelude::{BigInt, Either3, Either4, Uint8Array},
Either,
};
use napi_derive::napi;
use crate::{
cast::TryCast,
gas_report::GasReport,
solidity_tests::{artifact::ArtifactId, config::IncludeTraces},
trace::{solidity_stack_trace::SolidityStackTraceEntry, u256_to_bigint},
};
/// A grouping of value snapshot entries for a test.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct ValueSnapshotGroup {
/// The group name.
pub name: String,
/// The entries in the group.
pub entries: Vec<ValueSnapshotEntry>,
}
/// An entry in a value snapshot group.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct ValueSnapshotEntry {
/// The name of the entry.
pub name: String,
/// The value of the entry.
pub value: String,
}
/// See [`edr_solidity_tests::result::SuiteResult`]
#[napi]
#[derive(Clone, Debug)]
pub struct SuiteResult {
/// The artifact id can be used to match input to result in the progress
/// callback
#[napi(readonly)]
pub id: ArtifactId,
/// See [`edr_solidity_tests::result::SuiteResult::duration`]
#[napi(readonly)]
pub duration_ns: BigInt,
/// See [`edr_solidity_tests::result::SuiteResult::test_results`]
#[napi(readonly)]
pub test_results: Vec<TestResult>,
/// See [`edr_solidity_tests::result::SuiteResult::warnings`]
#[napi(readonly)]
pub warnings: Vec<String>,
}
impl SuiteResult {
pub fn new(
id: edr_solidity::artifacts::ArtifactId,
suite_result: edr_solidity_tests::result::SuiteResult<String>,
include_traces: IncludeTraces,
) -> Self {
Self {
id: id.into(),
duration_ns: BigInt::from(suite_result.duration.as_nanos()),
test_results: suite_result
.test_results
.into_iter()
.map(|(name, test_result)| TestResult::new(name, test_result, include_traces))
.collect(),
warnings: suite_result.warnings,
}
}
}
/// See [`edr_solidity_tests::result::TestResult`]
#[napi]
#[derive(Clone, Debug)]
pub struct TestResult {
/// The name of the test.
#[napi(readonly)]
pub name: String,
/// See [`edr_solidity_tests::result::TestResult::status`]
#[napi(readonly)]
pub status: TestStatus,
/// See [`edr_solidity_tests::result::TestResult::reason`]
#[napi(readonly)]
pub reason: Option<String>,
/// See [`edr_solidity_tests::result::TestResult::counterexample`]
#[napi(readonly)]
pub counterexample: Option<Either<BaseCounterExample, CounterExampleSequence>>,
/// See [`edr_solidity_tests::result::TestResult::decoded_logs`]
#[napi(readonly)]
pub decoded_logs: Vec<String>,
/// See [`edr_solidity_tests::result::TestResult::kind`]
#[napi(readonly)]
pub kind: Either3<StandardTestKind, FuzzTestKind, InvariantTestKind>,
/// See [`edr_solidity_tests::result::TestResult::duration`]
#[napi(readonly)]
pub duration_ns: BigInt,
/// Groups of value snapshot entries (incl. gas).
///
/// Only present if the test runner collected scoped snapshots. Currently,
/// this is always the case.
#[napi(readonly)]
pub value_snapshot_groups: Option<Vec<ValueSnapshotGroup>>,
stack_trace_result: Option<Arc<StackTraceResult<String>>>,
call_trace_arenas: Vec<(traces::TraceKind, SparsedTraceArena)>,
}
/// The stack trace result
#[napi(object)]
pub struct StackTrace {
/// Enum tag for JS.
#[napi(ts_type = "\"StackTrace\"")]
pub kind: &'static str,
/// The stack trace entries
pub entries: Vec<SolidityStackTraceEntry>,
}
/// We couldn't generate stack traces, because an unexpected error occurred.
#[napi(object)]
pub struct UnexpectedError {
/// Enum tag for JS.
#[napi(ts_type = "\"UnexpectedError\"")]
pub kind: &'static str,
/// The error message from the unexpected error.
pub error_message: String,
}
/// We couldn't generate stack traces, because the stack trace generation
/// heuristics failed due to an unknown reason.
#[napi(object)]
pub struct HeuristicFailed {
/// Enum tag for JS.
#[napi(ts_type = "\"HeuristicFailed\"")]
pub kind: &'static str,
}
/// We couldn't generate stack traces, because the test execution is unsafe to
/// replay due to indeterminism. This can be caused by either specifying a fork
/// url without a fork block number in the test runner config or using impure
/// cheatcodes.
#[napi(object)]
pub struct UnsafeToReplay {
/// Enum tag for JS.
#[napi(ts_type = "\"UnsafeToReplay\"")]
pub kind: &'static str,
/// Indeterminism due to specifying a fork url without a fork block number
/// in the test runner config.
pub global_fork_latest: bool,
/// The list of executed impure cheatcode signatures. We collect function
/// signatures instead of function names as whether a cheatcode is impure
/// can depend on the arguments it takes (e.g. `createFork` without a second
/// argument means implicitly fork from “latest”). Example signature:
/// `function createSelectFork(string calldata urlOrAlias) external returns
/// (uint256 forkId);`.
pub impure_cheatcodes: Vec<String>,
}
#[napi]
impl TestResult {
/// Compute the error stack trace.
/// The result is either the stack trace or the reason why we couldn't
/// generate the stack trace.
/// Returns null if the test status is succeeded or skipped.
/// Cannot throw.
#[napi]
pub fn stack_trace(
&self,
) -> Option<Either4<StackTrace, UnexpectedError, HeuristicFailed, UnsafeToReplay>> {
self.stack_trace_result.as_ref().map(|stack_trace_result| {
match stack_trace_result.as_ref() {
StackTraceResult::Success(stack_trace) => Either4::A(StackTrace {
kind: "StackTrace",
entries: stack_trace
.iter()
.cloned()
.map(TryCast::try_cast)
.collect::<Result<Vec<_>, Infallible>>()
.expect("infallible"),
}),
StackTraceResult::Error(error) => Either4::B(UnexpectedError {
kind: "UnexpectedError",
error_message: error.to_string(),
}),
StackTraceResult::HeuristicFailed => Either4::C(HeuristicFailed {
kind: "HeuristicFailed",
}),
StackTraceResult::UnsafeToReplay {
global_fork_latest,
impure_cheatcodes,
} => Either4::D(UnsafeToReplay {
kind: "UnsafeToReplay",
global_fork_latest: *global_fork_latest,
// napi-rs would clone `&'static str` under the hood anyway, so no performance
// hit from `Cow::into_owned`.
impure_cheatcodes: impure_cheatcodes
.iter()
.cloned()
.map(Cow::into_owned)
.collect(),
}),
}
})
}
/// Constructs the execution traces for the test. Returns an empty array if
/// traces for this test were not requested according to
/// [`crate::solidity_tests::config::SolidityTestRunnerConfigArgs::include_traces`]. Otherwise, returns
/// an array of the root calls of the trace, which always includes the test
/// call itself and may also include the setup call if there is one
/// (identified by the function name `setUp`).
#[napi]
pub fn call_traces(&self) -> Vec<CallTrace> {
self.call_trace_arenas
.iter()
.filter(|(k, _)| *k != traces::TraceKind::Deployment)
.map(|(_, a)| CallTrace::from_arena_node(&a.resolve_arena(), 0))
.collect()
}
}
impl TestResult {
fn new(
name: String,
test_result: edr_solidity_tests::result::TestResult<String>,
include_traces: IncludeTraces,
) -> Self {
let include_trace = include_traces == IncludeTraces::All
|| (include_traces == IncludeTraces::Failing && test_result.status.is_failure());
Self {
name,
status: test_result.status.into(),
reason: test_result.reason,
counterexample: test_result
.counterexample
.map(|counterexample| match counterexample {
edr_solidity_tests::fuzz::CounterExample::Single(counterexample) => {
Either::A(BaseCounterExample::from(counterexample))
}
edr_solidity_tests::fuzz::CounterExample::Sequence(
original_size,
counterexamples,
) => Either::B(CounterExampleSequence {
original_sequence_size: u64::try_from(original_size)
.expect("usize fits into u64")
.into(),
sequence: counterexamples
.into_iter()
.map(BaseCounterExample::from)
.collect(),
}),
}),
decoded_logs: test_result.decoded_logs,
kind: match test_result.kind {
edr_solidity_tests::result::TestKind::Unit { gas: gas_consumed } => {
Either3::A(StandardTestKind {
consumed_gas: BigInt::from(gas_consumed),
})
}
edr_solidity_tests::result::TestKind::Fuzz {
runs,
mean_gas,
median_gas,
} => Either3::B(FuzzTestKind {
// usize as u64 is always safe
runs: BigInt::from(runs as u64),
mean_gas: BigInt::from(mean_gas),
median_gas: BigInt::from(median_gas),
}),
edr_solidity_tests::result::TestKind::Invariant {
runs,
calls,
reverts,
metrics,
failed_corpus_replays,
} => Either3::C(InvariantTestKind {
// usize as u64 is always safe
runs: BigInt::from(runs as u64),
calls: BigInt::from(calls as u64),
reverts: BigInt::from(reverts as u64),
metrics: metrics
.into_iter()
.map(|(name, metric)| {
(
name,
InvariantMetrics {
calls: BigInt::from(metric.calls as u64),
reverts: BigInt::from(metric.reverts as u64),
discards: BigInt::from(metric.discards as u64),
},
)
})
.collect(),
failed_corpus_replays: BigInt::from(failed_corpus_replays as u64),
}),
},
duration_ns: BigInt::from(test_result.duration.as_nanos()),
value_snapshot_groups: Some(
test_result
.value_snapshots
.into_iter()
.map(|(group_name, entries)| ValueSnapshotGroup {
name: group_name,
entries: entries
.into_iter()
.map(|(name, value)| ValueSnapshotEntry { name, value })
.collect(),
})
.collect(),
),
stack_trace_result: test_result.stack_trace_result.map(Arc::new),
call_trace_arenas: if include_trace {
test_result.traces
} else {
vec![]
},
}
}
}
#[derive(Debug)]
#[napi(string_enum)]
#[doc = "The result of a test execution."]
pub enum TestStatus {
#[doc = "Test success"]
Success,
#[doc = "Test failure"]
Failure,
#[doc = "Test skipped"]
Skipped,
}
impl From<edr_solidity_tests::result::TestStatus> for TestStatus {
fn from(value: edr_solidity_tests::result::TestStatus) -> Self {
match value {
edr_solidity_tests::result::TestStatus::Success => Self::Success,
edr_solidity_tests::result::TestStatus::Failure => Self::Failure,
edr_solidity_tests::result::TestStatus::Skipped => Self::Skipped,
}
}
}
/// See [`edr_solidity_tests::result::TestKind::Unit`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct StandardTestKind {
/// The gas consumed by the test.
#[napi(readonly)]
pub consumed_gas: BigInt,
}
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct FuzzTestKind {
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub runs: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub mean_gas: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Fuzz`]
#[napi(readonly)]
pub median_gas: BigInt,
}
/// See [`edr_solidity_tests::fuzz::FuzzCase`]
#[napi(object)]
#[derive(Clone)]
pub struct FuzzCase {
/// The calldata used for this fuzz test
#[napi(readonly)]
pub calldata: Uint8Array,
/// Consumed gas
#[napi(readonly)]
pub gas: BigInt,
/// The initial gas stipend for the transaction
#[napi(readonly)]
pub stipend: BigInt,
}
impl Debug for FuzzCase {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FuzzCase")
.field("gas", &self.gas)
.field("stipend", &self.stipend)
.finish()
}
}
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct InvariantTestKind {
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub runs: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub calls: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub reverts: BigInt,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub metrics: std::collections::HashMap<String, InvariantMetrics>,
/// See [`edr_solidity_tests::result::TestKind::Invariant`]
#[napi(readonly)]
pub failed_corpus_replays: BigInt,
}
/// See [`edr_solidity_tests::result::InvariantMetrics`]
#[napi(object)]
#[derive(Debug, Clone)]
pub struct InvariantMetrics {
// Count of fuzzed selector calls.
#[napi(readonly)]
pub calls: BigInt,
// Count of fuzzed selector reverts.
#[napi(readonly)]
pub reverts: BigInt,
// Count of fuzzed selector discards (through assume cheatcodes).
#[napi(readonly)]
pub discards: BigInt,
}
/// Original sequence size and sequence of calls used as a counter example
/// for invariant tests.
#[napi(object)]
#[derive(Clone, Debug)]
pub struct CounterExampleSequence {
/// The original sequence size before shrinking.
pub original_sequence_size: BigInt,
/// The shrunk counterexample sequence.
pub sequence: Vec<BaseCounterExample>,
}
/// See [`edr_solidity_tests::fuzz::BaseCounterExample`]
#[napi(object)]
#[derive(Clone)]
pub struct BaseCounterExample {
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::sender`]
#[napi(readonly)]
pub sender: Option<Uint8Array>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::addr`]
#[napi(readonly)]
pub address: Option<Uint8Array>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::calldata`]
#[napi(readonly)]
pub calldata: Uint8Array,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::contract_name`]
#[napi(readonly)]
pub contract_name: Option<String>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::signature`]
#[napi(readonly)]
pub signature: Option<String>,
/// See [`edr_solidity_tests::fuzz::BaseCounterExample::args`]
#[napi(readonly)]
pub args: Option<String>,
}
impl Debug for BaseCounterExample {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BaseCounterExample")
.field("contract_name", &self.contract_name)
.field("signature", &self.signature)
.field("args", &self.args)
.finish()
}
}
impl From<edr_solidity_tests::fuzz::BaseCounterExample> for BaseCounterExample {
fn from(value: edr_solidity_tests::fuzz::BaseCounterExample) -> Self {
Self {
sender: value.sender.map(Uint8Array::with_data_copied),
address: value.addr.map(Uint8Array::with_data_copied),
calldata: Uint8Array::with_data_copied(value.calldata),
contract_name: value.contract_name,
signature: value.signature,
args: value.args,
}
}
}
/// Object representing a call in an execution trace, including contract
/// creation.
#[napi(object)]
pub struct CallTrace {
/// The kind of call or contract creation this represents.
pub kind: CallKind,
/// Whether the call succeeded or reverted.
pub success: bool,
/// Whether the call is a cheatcode.
pub is_cheatcode: bool,
/// The amount of gas that was consumed.
pub gas_used: BigInt,
/// The amount of native token that was included with the call.
pub value: BigInt,
/// The target address of the call.
pub address: String,
/// The name of the contract that is the target of the call, if known.
pub contract: Option<String>,
/// The input (calldata) to the call. If it encodes a known function call,
/// it will be decoded into the function name and a list of arguments.
/// For example, `{ name: "ownerOf", arguments: ["1"] }`. Note that the
/// function name may also be any of the special `fallback` and `receive`
/// functions. Otherwise, it will be provided as a raw byte array.
pub inputs: Either<DecodedTraceParameters, Uint8Array>,
/// The output of the call. This will be a decoded human-readable
/// representation of the value if the function is known, otherwise a
/// raw byte array.
pub outputs: Either<String, Uint8Array>,
/// Interleaved subcalls and event logs. Use `kind` to check if each member
/// of the array is a call or log trace.
pub children: Vec<Either<CallTrace, LogTrace>>,
}
/// Object representing an event log in an execution trace.
#[napi(object)]
pub struct LogTrace {
/// A constant to help discriminate the union `CallTrace | LogTrace`.
pub kind: LogKind,
/// If the log is a known event (based on its first topic), it will be
/// decoded into the event name and list of named parameters. For
/// example, `{ name: "Log", arguments: ["value: 1"] }`. Otherwise, it
/// will be provided as an array where all but the last element are the
/// log topics, and the last element is the log data.
pub parameters: Either<DecodedTraceParameters, Vec<Uint8Array>>,
}
/// The various kinds of call frames possible in the EVM.
#[napi]
#[derive(Debug)]
pub enum CallKind {
/// Regular call that may change state.
Call = 0,
/// Variant of `DelegateCall` that doesn't preserve sender or value in the
/// frame.
CallCode = 1,
/// Call that executes the code of the target in the context of the caller.
DelegateCall = 2,
/// Regular call that may not change state.
StaticCall = 3,
/// Contract creation.
Create = 4,
}
/// Kind marker for log traces.
#[napi]
#[derive(Debug)]
pub enum LogKind {
/// Single kind of log.
Log = 5,
// NOTE: The discriminants of LogKind and CallKind must be disjoint.
}
/// Decoded function call or event.
#[napi(object)]
pub struct DecodedTraceParameters {
/// The name of a function or an event.
pub name: String,
/// The arguments of the function call or the event, in their human-readable
/// representations.
pub arguments: Vec<String>,
}
impl CallTrace {
/// Instantiates a `CallTrace` with the details from a node and the supplied
/// children.
fn new(node: &traces::CallTraceNode, children: Vec<Either<CallTrace, LogTrace>>) -> Self {
let contract = node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.label.clone());
let address = node.trace.address.to_checksum(None);
let inputs = match &node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.call_data.as_ref())
{
Some(traces::DecodedCallData { signature, args }) => {
let name = signature
.split('(')
.next()
.expect("invalid function signature")
.to_string();
let arguments = args.clone();
Either::A(DecodedTraceParameters { name, arguments })
}
None => Either::B(node.trace.data.as_ref().into()),
};
let outputs = match node
.trace
.decoded
.as_ref()
.and_then(|decoded| decoded.return_data.as_ref())
{
Some(outputs) => Either::A(outputs.clone()),
None => {
if node.kind().is_any_create() && node.trace.success {
Either::A(format!("{} bytes of code", node.trace.output.len()))
} else {
Either::B(node.trace.output.as_ref().into())
}
}
};
Self {
kind: node.kind().into(),
success: node.trace.success,
is_cheatcode: node.trace.address == CHEATCODE_ADDRESS,
gas_used: node.trace.gas_used.into(),
value: u256_to_bigint(&node.trace.value),
contract,
address,
inputs,
outputs,
children,
}
}
/// Creates a tree of `CallTrace` rooted at some node in a trace arena.
fn from_arena_node(arena: &CallTraceArena, arena_index: usize) -> Self {
struct StackItem {
visited: bool,
parent_stack_index: Option<usize>,
arena_index: usize,
child_traces: Vec<Option<CallTrace>>,
}
let mut stack = Vec::new();
stack.push(StackItem {
visited: false,
arena_index,
parent_stack_index: None,
child_traces: Vec::new(),
});
loop {
// We will break out of the loop before the stack goes empty.
let mut item = stack.pop().unwrap();
let node = arena
.nodes()
.get(item.arena_index)
.expect("Arena index should be valid");
if item.visited {
let mut logs = node
.logs
.iter()
.map(|log| Some(LogTrace::from(log)))
.collect::<Vec<_>>();
let children = node
.ordering
.iter()
.filter_map(|ord| match *ord {
traces::TraceMemberOrder::Log(i) => {
let log = logs
.get_mut(i)
.expect("Log index should be valid")
.take()
.unwrap();
Some(Either::B(log))
}
traces::TraceMemberOrder::Call(i) => {
let child_trace = item
.child_traces
.get_mut(i)
.expect("Child trace index should be valid")
.take()
.unwrap();
Some(Either::A(child_trace))
}
traces::TraceMemberOrder::Step(_) => None,
})
.collect();
let trace = CallTrace::new(node, children);
if let Some(parent_stack_index) = item.parent_stack_index {
let parent = stack
.get_mut(parent_stack_index)
.expect("Parent stack index should be valid");
parent.child_traces.push(Some(trace));
} else {
return trace;
}
} else {
item.visited = true;
item.child_traces.reserve(node.children.len());
stack.push(item);
let top_index = Some(stack.len() - 1);
// Push children in reverse order to result in linear traversal of the arena for
// cache efficiency, on the assumption that the arena contains a pre-order
// traversal of the trace.
stack.extend(node.children.iter().rev().map(|&arena_index| StackItem {
visited: false,
parent_stack_index: top_index,
arena_index,
child_traces: Vec::new(),
}));
}
}
}
}
impl From<&traces::CallLog> for LogTrace {
fn from(log: &traces::CallLog) -> Self {
let decoded_log = log
.decoded
.as_ref()
.and_then(|decoded| decoded.name.clone().zip(decoded.params.as_ref()));
let parameters = decoded_log.map_or_else(
|| {
let raw_log = &log.raw_log;
let mut params = Vec::with_capacity(raw_log.topics().len() + 1);
params.extend(raw_log.topics().iter().map(|topic| topic.as_slice().into()));
params.push(log.raw_log.data.as_ref().into());
Either::B(params)
},
|(name, params)| {
let arguments = params
.iter()
.map(|(name, value)| format!("{name}: {value}"))
.collect();
Either::A(DecodedTraceParameters { name, arguments })
},
);
Self {
kind: LogKind::Log,
parameters,
}
}
}
impl From<traces::CallKind> for CallKind {
fn from(value: traces::CallKind) -> Self {
match value {
traces::CallKind::Call => CallKind::Call,
traces::CallKind::StaticCall => CallKind::StaticCall,
traces::CallKind::CallCode => CallKind::CallCode,
traces::CallKind::DelegateCall => CallKind::DelegateCall,
traces::CallKind::Create | traces::CallKind::Create2 => CallKind::Create,
// We do not support these EVM features.
traces::CallKind::AuthCall => {
unreachable!("Unsupported EVM features")
}
}
}
}
/// The result of a Solidity test run.
#[napi(object)]
pub struct SolidityTestResult {
/// Gas report, if it was generated.
#[napi(readonly)]
pub gas_report: Option<GasReport>,
}
impl From<edr_solidity_tests::multi_runner::SolidityTestResult> for SolidityTestResult {
fn from(value: edr_solidity_tests::multi_runner::SolidityTestResult) -> Self {
Self {
gas_report: value.gas_report.map(GasReport::from),
}
}
}

View File

@@ -0,0 +1,32 @@
use napi::{bindgen_prelude::BigInt, JsFunction};
use napi_derive::napi;
/// Configuration for subscriptions.
#[napi(object)]
pub struct SubscriptionConfig {
/// Callback to be called when a new event is received.
#[napi(ts_type = "(event: SubscriptionEvent) => void")]
pub subscription_callback: JsFunction,
}
impl From<edr_napi_core::subscription::Config> for SubscriptionConfig {
fn from(config: edr_napi_core::subscription::Config) -> Self {
Self {
subscription_callback: config.subscription_callback,
}
}
}
impl From<SubscriptionConfig> for edr_napi_core::subscription::Config {
fn from(config: SubscriptionConfig) -> Self {
Self {
subscription_callback: config.subscription_callback,
}
}
}
#[napi(object)]
pub struct SubscriptionEvent {
pub filter_id: BigInt,
pub result: serde_json::Value,
}

199
dev/env/node_modules/@nomicfoundation/edr/src/trace.rs generated vendored Executable file
View File

@@ -0,0 +1,199 @@
// In contrast to the functions in the `#[napi] impl XYZ` block,
// the free functions `#[napi] pub fn` are exported by napi-rs but
// are considered dead code in the (lib test) target.
// For now, we silence the relevant warnings, as we need to mimick
// the original API while we rewrite the stack trace refinement to Rust.
#![cfg_attr(test, allow(dead_code))]
use std::sync::Arc;
use edr_chain_spec::EvmHaltReason;
use edr_primitives::bytecode::opcode::OpCode;
use edr_tracing::BeforeMessage;
use napi::bindgen_prelude::{BigInt, Either3, Uint8Array};
use napi_derive::napi;
use crate::result::ExecutionResult;
mod library_utils;
mod debug;
mod exit;
mod model;
mod return_data;
pub mod solidity_stack_trace;
#[napi(object)]
pub struct TracingMessage {
/// Sender address
#[napi(readonly)]
pub caller: Uint8Array,
/// Recipient address. None if it is a Create message.
#[napi(readonly)]
pub to: Option<Uint8Array>,
/// Whether it's a static call
#[napi(readonly)]
pub is_static_call: bool,
/// Transaction gas limit
#[napi(readonly)]
pub gas_limit: BigInt,
/// Depth of the message
#[napi(readonly)]
pub depth: u8,
/// Input data of the message
#[napi(readonly)]
pub data: Uint8Array,
/// Value sent in the message
#[napi(readonly)]
pub value: BigInt,
/// Address of the code that is being executed. Can be different from `to`
/// if a delegate call is being done.
#[napi(readonly)]
pub code_address: Option<Uint8Array>,
/// Code of the contract that is being executed.
#[napi(readonly)]
pub code: Option<Uint8Array>,
}
impl From<&BeforeMessage> for TracingMessage {
fn from(value: &BeforeMessage) -> Self {
// Deconstruct to make sure all fields are handled
let BeforeMessage {
depth,
caller,
to,
is_static_call,
gas_limit,
data,
value,
code_address,
code,
} = value;
let data = Uint8Array::with_data_copied(data);
let code = code
.as_ref()
.map(|code| Uint8Array::with_data_copied(code.original_bytes()));
TracingMessage {
caller: Uint8Array::with_data_copied(caller),
to: to.as_ref().map(Uint8Array::with_data_copied),
gas_limit: BigInt::from(*gas_limit),
is_static_call: *is_static_call,
depth: *depth as u8,
data,
value: BigInt {
sign_bit: false,
words: value.into_limbs().to_vec(),
},
code_address: code_address.as_ref().map(Uint8Array::with_data_copied),
code,
}
}
}
#[napi(object)]
pub struct TracingStep {
/// Call depth
#[napi(readonly)]
pub depth: u8,
/// The program counter
#[napi(readonly)]
pub pc: BigInt,
/// The executed op code
#[napi(readonly)]
pub opcode: String,
/// The entries on the stack. It only contains the top element unless
/// verbose tracing is enabled. The vector is empty if there are no elements
/// on the stack.
#[napi(readonly)]
pub stack: Vec<BigInt>,
/// The memory at the step. None if verbose tracing is disabled.
#[napi(readonly)]
pub memory: Option<Uint8Array>,
}
impl TracingStep {
pub fn new(step: &edr_tracing::Step) -> Self {
let stack = step.stack.full().map_or_else(
|| {
step.stack
.top()
.map(u256_to_bigint)
.map_or_else(Vec::default, |top| vec![top])
},
|stack| stack.iter().map(u256_to_bigint).collect(),
);
let memory = step.memory.as_ref().map(Uint8Array::with_data_copied);
Self {
depth: step.depth as u8,
pc: BigInt::from(u64::from(step.pc)),
opcode: OpCode::name_by_op(step.opcode).to_string(),
stack,
memory,
}
}
}
pub(crate) fn u256_to_bigint(v: &edr_primitives::U256) -> BigInt {
BigInt {
sign_bit: false,
words: v.into_limbs().to_vec(),
}
}
#[napi(object)]
pub struct TracingMessageResult {
/// Execution result
#[napi(readonly)]
pub execution_result: ExecutionResult,
}
#[napi]
#[derive(Clone)]
pub struct RawTrace {
inner: Arc<edr_tracing::Trace<EvmHaltReason>>,
}
impl From<Arc<edr_tracing::Trace<EvmHaltReason>>> for RawTrace {
fn from(value: Arc<edr_tracing::Trace<EvmHaltReason>>) -> Self {
Self { inner: value }
}
}
#[napi]
impl RawTrace {
#[napi(getter)]
pub fn trace(&self) -> Vec<Either3<TracingMessage, TracingStep, TracingMessageResult>> {
self.inner
.messages
.iter()
.map(|message| match message {
edr_tracing::TraceMessage::Before(message) => {
Either3::A(TracingMessage::from(message))
}
edr_tracing::TraceMessage::Step(step) => Either3::B(TracingStep::new(step)),
edr_tracing::TraceMessage::After(message) => Either3::C(TracingMessageResult {
execution_result: ExecutionResult::from(message),
}),
})
.collect()
}
}
#[napi]
/// Returns the latest version of solc that EDR officially
/// supports and is tested against.
pub fn get_latest_supported_solc_version() -> String {
"0.8.28".to_string()
}

61
dev/env/node_modules/@nomicfoundation/edr/src/trace/debug.rs generated vendored Executable file
View File

@@ -0,0 +1,61 @@
//! Port of `hardhat-network/stack-traces/debug.ts` from Hardhat.
use napi::bindgen_prelude::Either25;
use napi_derive::napi;
use super::solidity_stack_trace::{RevertErrorStackTraceEntry, SolidityStackTrace};
use crate::trace::return_data::ReturnData;
#[napi(catch_unwind)]
fn print_stack_trace(trace: SolidityStackTrace) -> napi::Result<()> {
let entry_values = trace
.into_iter()
.map(|entry| match entry {
Either25::A(entry) => serde_json::to_value(entry),
Either25::B(entry) => serde_json::to_value(entry),
Either25::C(entry) => serde_json::to_value(entry),
Either25::D(entry) => serde_json::to_value(entry),
Either25::F(entry) => serde_json::to_value(entry),
Either25::G(entry) => serde_json::to_value(entry),
Either25::H(entry) => serde_json::to_value(entry),
Either25::I(entry) => serde_json::to_value(entry),
Either25::J(entry) => serde_json::to_value(entry),
Either25::K(entry) => serde_json::to_value(entry),
Either25::L(entry) => serde_json::to_value(entry),
Either25::M(entry) => serde_json::to_value(entry),
Either25::N(entry) => serde_json::to_value(entry),
Either25::O(entry) => serde_json::to_value(entry),
Either25::P(entry) => serde_json::to_value(entry),
Either25::Q(entry) => serde_json::to_value(entry),
Either25::R(entry) => serde_json::to_value(entry),
Either25::S(entry) => serde_json::to_value(entry),
Either25::T(entry) => serde_json::to_value(entry),
Either25::U(entry) => serde_json::to_value(entry),
Either25::V(entry) => serde_json::to_value(entry),
Either25::W(entry) => serde_json::to_value(entry),
Either25::X(entry) => serde_json::to_value(entry),
Either25::Y(entry) => serde_json::to_value(entry),
// Decode the error message from the return data
Either25::E(entry @ RevertErrorStackTraceEntry { .. }) => {
use serde::de::Error;
let decoded_error_msg = ReturnData::new(entry.return_data.clone())
.decode_error()
.map_err(|e| {
serde_json::Error::custom(format_args!("Error decoding return data: {e}"))
})?;
let mut value = serde_json::to_value(entry)?;
if let Some(obj) = value.as_object_mut() {
obj.insert("message".to_string(), decoded_error_msg.into());
}
Ok(value)
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| napi::Error::from_reason(format!("Error converting to JSON: {e}")))?;
println!("{}", serde_json::to_string_pretty(&entry_values)?);
Ok(())
}

89
dev/env/node_modules/@nomicfoundation/edr/src/trace/exit.rs generated vendored Executable file
View File

@@ -0,0 +1,89 @@
//! Naive rewrite of `hardhat-network/provider/vm/exit.ts` from Hardhat.
//! Used together with `VmTracer`.
use std::fmt;
use edr_chain_spec::EvmHaltReason;
use napi_derive::napi;
#[napi]
pub struct Exit(pub(crate) ExitCode);
#[napi]
/// Represents the exit code of the EVM.
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::upper_case_acronyms, non_camel_case_types)] // These are exported and mapped 1:1 to existing JS enum
pub enum ExitCode {
/// Execution was successful.
SUCCESS = 0,
/// Execution was reverted.
REVERT,
/// Execution ran out of gas.
OUT_OF_GAS,
/// Execution encountered an internal error.
INTERNAL_ERROR,
/// Execution encountered an invalid opcode.
INVALID_OPCODE,
/// Execution encountered a stack underflow.
STACK_UNDERFLOW,
/// Create init code size exceeds limit (runtime).
CODESIZE_EXCEEDS_MAXIMUM,
/// Create collision.
CREATE_COLLISION,
/// Unknown halt reason.
UNKNOWN_HALT_REASON,
}
impl fmt::Display for ExitCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExitCode::SUCCESS => write!(f, "Success"),
ExitCode::REVERT => write!(f, "Reverted"),
ExitCode::OUT_OF_GAS => write!(f, "Out of gas"),
ExitCode::INTERNAL_ERROR => write!(f, "Internal error"),
ExitCode::INVALID_OPCODE => write!(f, "Invalid opcode"),
ExitCode::STACK_UNDERFLOW => write!(f, "Stack underflow"),
ExitCode::CODESIZE_EXCEEDS_MAXIMUM => write!(f, "Codesize exceeds maximum"),
ExitCode::CREATE_COLLISION => write!(f, "Create collision"),
ExitCode::UNKNOWN_HALT_REASON => write!(f, "Unknown halt reason"),
}
}
}
#[allow(clippy::fallible_impl_from)] // naively ported for now
impl From<edr_solidity::exit_code::ExitCode<EvmHaltReason>> for ExitCode {
fn from(code: edr_solidity::exit_code::ExitCode<EvmHaltReason>) -> Self {
use edr_solidity::exit_code::ExitCode;
match code {
ExitCode::Success => Self::SUCCESS,
ExitCode::Revert => Self::REVERT,
ExitCode::Halt(EvmHaltReason::OutOfGas(_)) => Self::OUT_OF_GAS,
ExitCode::Halt(EvmHaltReason::OpcodeNotFound | EvmHaltReason::InvalidFEOpcode
// Returned when an opcode is not implemented for the hardfork
| EvmHaltReason::NotActivated) => Self::INVALID_OPCODE,
ExitCode::Halt(EvmHaltReason::StackUnderflow) => Self::STACK_UNDERFLOW,
ExitCode::Halt(EvmHaltReason::CreateContractSizeLimit) => Self::CODESIZE_EXCEEDS_MAXIMUM,
ExitCode::Halt(EvmHaltReason::CreateCollision) => Self::CREATE_COLLISION,
_ => Self::UNKNOWN_HALT_REASON,
}
}
}
#[napi]
impl Exit {
#[napi(catch_unwind, getter)]
pub fn kind(&self) -> ExitCode {
self.0
}
#[napi(catch_unwind)]
pub fn is_error(&self) -> bool {
!matches!(self.0, ExitCode::SUCCESS)
}
#[napi(catch_unwind)]
pub fn get_reason(&self) -> String {
self.0.to_string()
}
}

View File

@@ -0,0 +1,11 @@
use napi_derive::napi;
#[napi(catch_unwind)]
pub fn link_hex_string_bytecode(
code: String,
address: String,
position: u32,
) -> napi::Result<String> {
edr_solidity::library_utils::link_hex_string_bytecode(code, &address, position)
.map_err(|err| napi::Error::from_reason(err.to_string()))
}

59
dev/env/node_modules/@nomicfoundation/edr/src/trace/model.rs generated vendored Executable file
View File

@@ -0,0 +1,59 @@
use std::rc::Rc;
use edr_solidity::build_model::ContractMetadata;
use napi_derive::napi;
use serde::Serialize;
/// Opaque handle to the `Bytecode` struct.
/// Only used on the JS side by the `VmTraceDecoder` class.
// NOTE: Needed, because we store the resolved `Bytecode` in the MessageTrace
// JS plain objects and those need a dedicated (class) type.
#[napi]
pub struct BytecodeWrapper(pub(crate) Rc<ContractMetadata>);
impl BytecodeWrapper {
pub fn new(bytecode: Rc<ContractMetadata>) -> Self {
Self(bytecode)
}
pub fn inner(&self) -> &Rc<ContractMetadata> {
&self.0
}
}
impl std::ops::Deref for BytecodeWrapper {
type Target = ContractMetadata;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, PartialEq, Eq, Serialize)]
#[allow(non_camel_case_types)] // intentionally mimicks the original case in TS
#[allow(clippy::upper_case_acronyms)]
#[napi]
// Mimicks [`edr_solidity::build_model::ContractFunctionType`].
pub enum ContractFunctionType {
CONSTRUCTOR,
FUNCTION,
FALLBACK,
RECEIVE,
GETTER,
MODIFIER,
FREE_FUNCTION,
}
impl From<edr_solidity::build_model::ContractFunctionType> for ContractFunctionType {
fn from(value: edr_solidity::build_model::ContractFunctionType) -> Self {
match value {
edr_solidity::build_model::ContractFunctionType::Constructor => Self::CONSTRUCTOR,
edr_solidity::build_model::ContractFunctionType::Function => Self::FUNCTION,
edr_solidity::build_model::ContractFunctionType::Fallback => Self::FALLBACK,
edr_solidity::build_model::ContractFunctionType::Receive => Self::RECEIVE,
edr_solidity::build_model::ContractFunctionType::Getter => Self::GETTER,
edr_solidity::build_model::ContractFunctionType::Modifier => Self::MODIFIER,
edr_solidity::build_model::ContractFunctionType::FreeFunction => Self::FREE_FUNCTION,
}
}
}

View File

@@ -0,0 +1,96 @@
//! Rewrite of `hardhat-network/provider/return-data.ts` from Hardhat.
use alloy_sol_types::SolError;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
// Built-in error types
// See <https://docs.soliditylang.org/en/v0.8.26/control-structures.html#error-handling-assert-require-revert-and-exceptions>
alloy_sol_types::sol! {
error Error(string);
error Panic(uint256);
}
#[napi]
pub struct ReturnData {
#[napi(readonly)]
pub value: Uint8Array,
selector: Option<[u8; 4]>,
}
#[napi]
impl ReturnData {
#[napi(catch_unwind, constructor)]
pub fn new(value: Uint8Array) -> Self {
let selector = value
.get(0..4)
.map(|selector| selector.try_into().expect("selector is 4 bytes"));
Self { value, selector }
}
#[napi(catch_unwind)]
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn matches_selector(&self, selector: impl AsRef<[u8]>) -> bool {
self.selector
.is_some_and(|value| value == selector.as_ref())
}
#[napi(catch_unwind)]
pub fn is_error_return_data(&self) -> bool {
self.selector == Some(Error::SELECTOR)
}
#[napi(catch_unwind)]
pub fn is_panic_return_data(&self) -> bool {
self.selector == Some(Panic::SELECTOR)
}
#[napi(catch_unwind)]
pub fn decode_error(&self) -> napi::Result<String> {
if self.is_empty() {
return Ok(String::new());
}
if !self.is_error_return_data() {
return Err(napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to be a Error(string)",
));
}
let result = Error::abi_decode(&self.value[..]).map_err(|_err| {
napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to contain a valid string",
)
})?;
Ok(result.0)
}
#[napi(catch_unwind)]
pub fn decode_panic(&self) -> napi::Result<BigInt> {
if !self.is_panic_return_data() {
return Err(napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to be a Panic(uint256)",
));
}
let result = Panic::abi_decode(&self.value[..]).map_err(|_err| {
napi::Error::new(
napi::Status::InvalidArg,
"VM Exception while processing transaction: Expected return data to contain a valid uint256",
)
})?;
Ok(BigInt {
sign_bit: false,
words: result.0.as_limbs().to_vec(),
})
}
}

View File

@@ -0,0 +1,883 @@
//! Naive rewrite of `hardhat-network/stack-traces/solidity-stack-traces.ts`
//! from Hardhat.
use std::convert::Infallible;
use edr_primitives::{hex, U256};
use napi::bindgen_prelude::{BigInt, Either25, FromNapiValue, ToNapiValue, Uint8Array, Undefined};
use napi_derive::napi;
use serde::{Serialize, Serializer};
use super::model::ContractFunctionType;
use crate::{
cast::TryCast, solidity_tests::cheatcode_errors::CheatcodeErrorDetails, trace::u256_to_bigint,
};
#[napi]
#[repr(u8)]
#[allow(non_camel_case_types)] // intentionally mimicks the original case in TS
#[allow(clippy::upper_case_acronyms)]
#[derive(PartialEq, Eq, PartialOrd, Ord, strum::FromRepr, strum::IntoStaticStr, Serialize)]
pub enum StackTraceEntryType {
CALLSTACK_ENTRY = 0,
UNRECOGNIZED_CREATE_CALLSTACK_ENTRY,
UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY,
PRECOMPILE_ERROR,
REVERT_ERROR,
PANIC_ERROR,
CUSTOM_ERROR,
FUNCTION_NOT_PAYABLE_ERROR,
INVALID_PARAMS_ERROR,
FALLBACK_NOT_PAYABLE_ERROR,
FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR,
UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR, /* TODO: Should trying to call a
* private/internal be a special case of
* this? */
MISSING_FALLBACK_OR_RECEIVE_ERROR,
RETURNDATA_SIZE_ERROR,
NONCONTRACT_ACCOUNT_CALLED_ERROR,
CALL_FAILED_ERROR,
DIRECT_LIBRARY_CALL_ERROR,
UNRECOGNIZED_CREATE_ERROR,
UNRECOGNIZED_CONTRACT_ERROR,
OTHER_EXECUTION_ERROR,
// This is a special case to handle a regression introduced in solc 0.6.3
// For more info: https://github.com/ethereum/solidity/issues/9006
UNMAPPED_SOLC_0_6_3_REVERT_ERROR,
CONTRACT_TOO_LARGE_ERROR,
INTERNAL_FUNCTION_CALLSTACK_ENTRY,
CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR,
CHEATCODE_ERROR,
}
#[napi(catch_unwind)]
pub fn stack_trace_entry_type_to_string(val: StackTraceEntryType) -> &'static str {
val.into()
}
#[napi]
pub const FALLBACK_FUNCTION_NAME: &str = "<fallback>";
#[napi]
pub const RECEIVE_FUNCTION_NAME: &str = "<receive>";
#[napi]
pub const CONSTRUCTOR_FUNCTION_NAME: &str = "constructor";
#[napi]
pub const UNRECOGNIZED_FUNCTION_NAME: &str = "<unrecognized-selector>";
#[napi]
pub const UNKNOWN_FUNCTION_NAME: &str = "<unknown>";
#[napi]
pub const PRECOMPILE_FUNCTION_NAME: &str = "<precompile>";
#[napi]
pub const UNRECOGNIZED_CONTRACT_NAME: &str = "<UnrecognizedContract>";
#[napi(object)]
#[derive(Clone, PartialEq, Serialize)]
pub struct SourceReference {
pub source_name: String,
pub source_content: String,
pub contract: Option<String>,
pub function: Option<String>,
pub line: u32,
// [number, number] tuple
pub range: Vec<u32>,
}
impl From<edr_solidity::solidity_stack_trace::SourceReference> for SourceReference {
fn from(value: edr_solidity::solidity_stack_trace::SourceReference) -> Self {
let (range_start, range_end) = value.range;
Self {
source_name: value.source_name,
source_content: value.source_content,
contract: value.contract,
function: value.function,
line: value.line,
range: vec![range_start, range_end],
}
}
}
impl From<edr_solidity::return_data::CheatcodeErrorDetails> for CheatcodeErrorDetails {
fn from(value: edr_solidity::return_data::CheatcodeErrorDetails) -> Self {
Self {
code: value.code.into(),
cheatcode: value.cheatcode,
}
}
}
/// A [`StackTraceEntryType`] constant that is convertible to/from a
/// `napi_value`.
///
/// Since Rust does not allow constants directly as members, we use this wrapper
/// to allow the `StackTraceEntryType` to be used as a member of an interface
/// when defining the N-API bindings.
// NOTE: It's currently not possible to use an enum as const generic parameter,
// so we use the underlying `u8` repr used by the enum.
#[derive(Clone, Copy)]
pub struct StackTraceEntryTypeConst<const ENTRY_TYPE: u8>;
impl<const ENTRY_TYPE: u8> FromNapiValue for StackTraceEntryTypeConst<ENTRY_TYPE> {
unsafe fn from_napi_value(
env: napi::sys::napi_env,
napi_val: napi::sys::napi_value,
) -> napi::Result<Self> {
// SAFETY: The safety concern is propagated in the function signature.
let inner: u8 = unsafe { FromNapiValue::from_napi_value(env, napi_val) }?;
if inner != ENTRY_TYPE {
return Err(napi::Error::new(
napi::Status::InvalidArg,
format!("Expected StackTraceEntryType value: {ENTRY_TYPE}, got: {inner}"),
));
}
Ok(StackTraceEntryTypeConst)
}
}
impl<const ENTRY_TYPE: u8> ToNapiValue for StackTraceEntryTypeConst<ENTRY_TYPE> {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
_val: Self,
) -> napi::Result<napi::sys::napi_value> {
// SAFETY: The safety concern is propagated in the function signature.
unsafe { u8::to_napi_value(env, ENTRY_TYPE) }
}
}
impl<const ENTRY_TYPE: u8> Serialize for StackTraceEntryTypeConst<ENTRY_TYPE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let inner = StackTraceEntryType::from_repr(ENTRY_TYPE).ok_or_else(|| {
serde::ser::Error::custom(format!("Invalid StackTraceEntryType value: {ENTRY_TYPE}"))
})?;
inner.serialize(serializer)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CallstackEntryStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CALLSTACK_ENTRY")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CALLSTACK_ENTRY as u8 }>,
pub source_reference: SourceReference,
pub function_type: ContractFunctionType,
}
impl From<CallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CallstackEntryStackTraceEntry) -> Self {
Either25::A(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedCreateCallstackEntryStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CREATE_CALLSTACK_ENTRY"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_CREATE_CALLSTACK_ENTRY as u8 },
>,
pub source_reference: Option<Undefined>,
}
impl From<UnrecognizedCreateCallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedCreateCallstackEntryStackTraceEntry) -> Self {
Either25::B(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedContractCallstackEntryStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY as u8 },
>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub address: Uint8Array,
pub source_reference: Option<Undefined>,
}
impl From<UnrecognizedContractCallstackEntryStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedContractCallstackEntryStackTraceEntry) -> Self {
Either25::C(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct PrecompileErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.PRECOMPILE_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::PRECOMPILE_ERROR as u8 }>,
pub precompile: u32,
pub source_reference: Option<Undefined>,
}
impl From<PrecompileErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: PrecompileErrorStackTraceEntry) -> Self {
Either25::D(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct RevertErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.REVERT_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::REVERT_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: SourceReference,
pub is_invalid_opcode_error: bool,
}
impl From<RevertErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: RevertErrorStackTraceEntry) -> Self {
Either25::E(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct PanicErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.PANIC_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::PANIC_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub error_code: BigInt,
pub source_reference: Option<SourceReference>,
}
impl From<PanicErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: PanicErrorStackTraceEntry) -> Self {
Either25::F(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CustomErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CUSTOM_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CUSTOM_ERROR as u8 }>,
// unlike RevertErrorStackTraceEntry, this includes the message already parsed
pub message: String,
pub source_reference: SourceReference,
}
impl From<CustomErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CustomErrorStackTraceEntry) -> Self {
Either25::G(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FunctionNotPayableErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FUNCTION_NOT_PAYABLE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::FUNCTION_NOT_PAYABLE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FunctionNotPayableErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FunctionNotPayableErrorStackTraceEntry) -> Self {
Either25::H(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct InvalidParamsErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.INVALID_PARAMS_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::INVALID_PARAMS_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<InvalidParamsErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: InvalidParamsErrorStackTraceEntry) -> Self {
Either25::I(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FallbackNotPayableErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FALLBACK_NOT_PAYABLE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::FALLBACK_NOT_PAYABLE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FallbackNotPayableErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FallbackNotPayableErrorStackTraceEntry) -> Self {
Either25::J(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct FallbackNotPayableAndNoReceiveErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::FALLBACK_NOT_PAYABLE_AND_NO_RECEIVE_ERROR as u8 },
>,
#[serde(serialize_with = "serialize_evm_value_bigint_using_u256")]
pub value: BigInt,
pub source_reference: SourceReference,
}
impl From<FallbackNotPayableAndNoReceiveErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: FallbackNotPayableAndNoReceiveErrorStackTraceEntry) -> Self {
Either25::K(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR"
)]
pub type_: StackTraceEntryTypeConst<
{ StackTraceEntryType::UNRECOGNIZED_FUNCTION_WITHOUT_FALLBACK_ERROR as u8 },
>,
pub source_reference: SourceReference,
}
impl From<UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry) -> Self {
Either25::L(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct MissingFallbackOrReceiveErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.MISSING_FALLBACK_OR_RECEIVE_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::MISSING_FALLBACK_OR_RECEIVE_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<MissingFallbackOrReceiveErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: MissingFallbackOrReceiveErrorStackTraceEntry) -> Self {
Either25::M(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ReturndataSizeErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.RETURNDATA_SIZE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::RETURNDATA_SIZE_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<ReturndataSizeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: ReturndataSizeErrorStackTraceEntry) -> Self {
Either25::N(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct NonContractAccountCalledErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.NONCONTRACT_ACCOUNT_CALLED_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::NONCONTRACT_ACCOUNT_CALLED_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<NonContractAccountCalledErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: NonContractAccountCalledErrorStackTraceEntry) -> Self {
Either25::O(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CallFailedErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CALL_FAILED_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CALL_FAILED_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<CallFailedErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CallFailedErrorStackTraceEntry) -> Self {
Either25::P(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct DirectLibraryCallErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.DIRECT_LIBRARY_CALL_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::DIRECT_LIBRARY_CALL_ERROR as u8 }>,
pub source_reference: SourceReference,
}
impl From<DirectLibraryCallErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: DirectLibraryCallErrorStackTraceEntry) -> Self {
Either25::Q(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedCreateErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CREATE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::UNRECOGNIZED_CREATE_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: Option<Undefined>,
pub is_invalid_opcode_error: bool,
}
impl From<UnrecognizedCreateErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedCreateErrorStackTraceEntry) -> Self {
Either25::R(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnrecognizedContractErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNRECOGNIZED_CONTRACT_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::UNRECOGNIZED_CONTRACT_ERROR as u8 }>,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub address: Uint8Array,
#[serde(serialize_with = "serialize_uint8array_to_hex")]
pub return_data: Uint8Array,
pub source_reference: Option<Undefined>,
pub is_invalid_opcode_error: bool,
}
impl From<UnrecognizedContractErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnrecognizedContractErrorStackTraceEntry) -> Self {
Either25::S(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct OtherExecutionErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.OTHER_EXECUTION_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::OTHER_EXECUTION_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<OtherExecutionErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: OtherExecutionErrorStackTraceEntry) -> Self {
Either25::T(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct UnmappedSolc063RevertErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.UNMAPPED_SOLC_0_6_3_REVERT_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::UNMAPPED_SOLC_0_6_3_REVERT_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<UnmappedSolc063RevertErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: UnmappedSolc063RevertErrorStackTraceEntry) -> Self {
Either25::U(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ContractTooLargeErrorStackTraceEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.CONTRACT_TOO_LARGE_ERROR"
)]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CONTRACT_TOO_LARGE_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<ContractTooLargeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: ContractTooLargeErrorStackTraceEntry) -> Self {
Either25::V(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct InternalFunctionCallStackEntry {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.INTERNAL_FUNCTION_CALLSTACK_ENTRY"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::INTERNAL_FUNCTION_CALLSTACK_ENTRY as u8 }>,
pub pc: u32,
pub source_reference: SourceReference,
}
impl From<InternalFunctionCallStackEntry> for SolidityStackTraceEntry {
fn from(val: InternalFunctionCallStackEntry) -> Self {
Either25::W(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct ContractCallRunOutOfGasError {
#[napi(
js_name = "type",
ts_type = "StackTraceEntryType.CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR"
)]
pub type_:
StackTraceEntryTypeConst<{ StackTraceEntryType::CONTRACT_CALL_RUN_OUT_OF_GAS_ERROR as u8 }>,
pub source_reference: Option<SourceReference>,
}
impl From<ContractCallRunOutOfGasError> for SolidityStackTraceEntry {
fn from(val: ContractCallRunOutOfGasError) -> Self {
Either25::X(val)
}
}
#[napi(object)]
#[derive(Clone, Serialize)]
pub struct CheatcodeErrorStackTraceEntry {
#[napi(js_name = "type", ts_type = "StackTraceEntryType.CHEATCODE_ERROR")]
pub type_: StackTraceEntryTypeConst<{ StackTraceEntryType::CHEATCODE_ERROR as u8 }>,
// The parsed cheatcode error message that can be displayed to the user
pub message: String,
pub source_reference: SourceReference,
pub details: Option<CheatcodeErrorDetails>,
}
impl From<CheatcodeErrorStackTraceEntry> for SolidityStackTraceEntry {
fn from(val: CheatcodeErrorStackTraceEntry) -> Self {
Either25::Y(val)
}
}
#[allow(dead_code)]
// NOTE: This ported directly from JS for completeness and is used in the Rust
// side of the bindings. However, napi-rs does not support exporting Rust type
// aliases to the index.d.ts file, and it does not store the type definitions
// when expanding the macros, so to use it we would have to specify this type
// literally (all 26 lines of it) at every #[napi]-exported function, which is
// not ideal.
// Rather, we just bite the bullet for now and use the type alias directly
// (which falls back to `any` as it's not recognized in the context of the
// index.d.ts file) until we finish the porting work.
pub type SolidityStackTraceEntry = Either25<
CallstackEntryStackTraceEntry,
UnrecognizedCreateCallstackEntryStackTraceEntry,
UnrecognizedContractCallstackEntryStackTraceEntry,
PrecompileErrorStackTraceEntry,
RevertErrorStackTraceEntry,
PanicErrorStackTraceEntry,
CustomErrorStackTraceEntry,
FunctionNotPayableErrorStackTraceEntry,
InvalidParamsErrorStackTraceEntry,
FallbackNotPayableErrorStackTraceEntry,
FallbackNotPayableAndNoReceiveErrorStackTraceEntry,
UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry,
MissingFallbackOrReceiveErrorStackTraceEntry,
ReturndataSizeErrorStackTraceEntry,
NonContractAccountCalledErrorStackTraceEntry,
CallFailedErrorStackTraceEntry,
DirectLibraryCallErrorStackTraceEntry,
UnrecognizedCreateErrorStackTraceEntry,
UnrecognizedContractErrorStackTraceEntry,
OtherExecutionErrorStackTraceEntry,
UnmappedSolc063RevertErrorStackTraceEntry,
ContractTooLargeErrorStackTraceEntry,
InternalFunctionCallStackEntry,
ContractCallRunOutOfGasError,
CheatcodeErrorStackTraceEntry,
>;
impl TryCast<SolidityStackTraceEntry> for edr_solidity::solidity_stack_trace::StackTraceEntry {
type Error = Infallible;
fn try_cast(self) -> Result<SolidityStackTraceEntry, Self::Error> {
use edr_solidity::solidity_stack_trace::StackTraceEntry;
let result = match self {
StackTraceEntry::CallstackEntry {
source_reference,
function_type,
} => CallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
function_type: function_type.into(),
}
.into(),
StackTraceEntry::UnrecognizedCreateCallstackEntry => {
UnrecognizedCreateCallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: None,
}
.into()
}
StackTraceEntry::UnrecognizedContractCallstackEntry { address } => {
UnrecognizedContractCallstackEntryStackTraceEntry {
type_: StackTraceEntryTypeConst,
address: Uint8Array::with_data_copied(address),
source_reference: None,
}
.into()
}
StackTraceEntry::PrecompileError { precompile } => PrecompileErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
precompile,
source_reference: None,
}
.into(),
StackTraceEntry::RevertError {
return_data,
source_reference,
is_invalid_opcode_error,
} => RevertErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
return_data: return_data.into(),
source_reference: source_reference.into(),
is_invalid_opcode_error,
}
.into(),
StackTraceEntry::PanicError {
error_code,
source_reference,
} => PanicErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
error_code: u256_to_bigint(&error_code),
source_reference: source_reference.map(std::convert::Into::into),
}
.into(),
StackTraceEntry::CheatCodeError {
message,
source_reference,
details,
} => CheatcodeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
message,
source_reference: source_reference.into(),
details: details.map(std::convert::Into::into),
}
.into(),
StackTraceEntry::CustomError {
message,
source_reference,
} => CustomErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
message,
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::FunctionNotPayableError {
value,
source_reference,
} => FunctionNotPayableErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::InvalidParamsError { source_reference } => {
InvalidParamsErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::FallbackNotPayableError {
value,
source_reference,
} => FallbackNotPayableErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::FallbackNotPayableAndNoReceiveError {
value,
source_reference,
} => FallbackNotPayableAndNoReceiveErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
value: u256_to_bigint(&value),
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::UnrecognizedFunctionWithoutFallbackError { source_reference } => {
UnrecognizedFunctionWithoutFallbackErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::MissingFallbackOrReceiveError { source_reference } => {
MissingFallbackOrReceiveErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::ReturndataSizeError { source_reference } => {
ReturndataSizeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::NoncontractAccountCalledError { source_reference } => {
NonContractAccountCalledErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::CallFailedError { source_reference } => {
CallFailedErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::DirectLibraryCallError { source_reference } => {
DirectLibraryCallErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.into(),
}
.into()
}
StackTraceEntry::UnrecognizedCreateError {
return_data,
is_invalid_opcode_error,
} => UnrecognizedCreateErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
return_data: return_data.into(),
is_invalid_opcode_error,
source_reference: None,
}
.into(),
StackTraceEntry::UnrecognizedContractError {
address,
return_data,
is_invalid_opcode_error,
} => UnrecognizedContractErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
address: Uint8Array::with_data_copied(address),
return_data: return_data.into(),
is_invalid_opcode_error,
source_reference: None,
}
.into(),
StackTraceEntry::OtherExecutionError { source_reference } => {
OtherExecutionErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::UnmappedSolc0_6_3RevertError { source_reference } => {
UnmappedSolc063RevertErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::ContractTooLargeError { source_reference } => {
ContractTooLargeErrorStackTraceEntry {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
StackTraceEntry::InternalFunctionCallstackEntry {
pc,
source_reference,
} => InternalFunctionCallStackEntry {
type_: StackTraceEntryTypeConst,
pc,
source_reference: source_reference.into(),
}
.into(),
StackTraceEntry::ContractCallRunOutOfGasError { source_reference } => {
ContractCallRunOutOfGasError {
type_: StackTraceEntryTypeConst,
source_reference: source_reference.map(std::convert::Into::into),
}
.into()
}
};
Ok(result)
}
}
#[allow(dead_code)]
// Same as above, but for the `SolidityStackTrace` type.
pub type SolidityStackTrace = Vec<SolidityStackTraceEntry>;
const _: () = {
const fn assert_to_from_napi_value<T: FromNapiValue + ToNapiValue>() {}
assert_to_from_napi_value::<SolidityStackTraceEntry>();
};
/// Serializes a [`BigInt`] that represents an EVM value as a
/// [`edr_primitives::U256`].
fn serialize_evm_value_bigint_using_u256<S>(bigint: &BigInt, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let val = U256::from_limbs_slice(&bigint.words);
val.serialize(s)
}
fn serialize_uint8array_to_hex<S>(uint8array: &Uint8Array, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex = hex::encode(uint8array.as_ref());
hex.serialize(s)
}

View File

@@ -0,0 +1,46 @@
import { StandardTestKind, FuzzTestKind, InvariantTestKind } from "../../index";
export enum SortOrder {
Ascending,
Descending,
}
export interface GasUsageFilter {
minThreshold?: bigint;
maxThreshold?: bigint;
}
export function extractGasUsage(
testResults: {
name: string;
kind: StandardTestKind | FuzzTestKind | InvariantTestKind;
}[],
filter?: GasUsageFilter,
ordering?: SortOrder
): { name: string; gas: bigint }[] {
const gasUsage: { name: string; gas: bigint }[] = [];
for (const result of testResults) {
// Default to zero gas for invariant tests
const gas = "consumedGas" in result.kind
? result.kind.consumedGas
: "medianGas" in result.kind
? result.kind.medianGas
: BigInt(0);
if (
(!filter?.minThreshold || gas >= filter.minThreshold) &&
(!filter?.maxThreshold || gas <= filter.maxThreshold)
) {
gasUsage.push({ name: result.name, gas });
}
}
if (ordering === SortOrder.Ascending) {
gasUsage.sort((a, b) => (a.gas < b.gas ? -1 : a.gas > b.gas ? 1 : 0));
} else if (ordering === SortOrder.Descending) {
gasUsage.sort((a, b) => (a.gas > b.gas ? -1 : a.gas < b.gas ? 1 : 0));
}
return gasUsage;
}

49
dev/env/node_modules/@nomicfoundation/edr/src/withdrawal.rs generated vendored Executable file
View File

@@ -0,0 +1,49 @@
use edr_primitives::Address;
use napi::bindgen_prelude::{BigInt, Uint8Array};
use napi_derive::napi;
use crate::cast::TryCast as _;
#[napi(object)]
pub struct Withdrawal {
/// The index of withdrawal
pub index: BigInt,
/// The index of the validator that generated the withdrawal
pub validator_index: BigInt,
/// The recipient address for withdrawal value
pub address: Uint8Array,
/// The value contained in withdrawal
pub amount: BigInt,
}
impl From<edr_block_header::Withdrawal> for Withdrawal {
fn from(withdrawal: edr_block_header::Withdrawal) -> Self {
Self {
index: BigInt::from(withdrawal.index),
validator_index: BigInt::from(withdrawal.validator_index),
address: Uint8Array::with_data_copied(withdrawal.address),
amount: BigInt {
sign_bit: false,
words: vec![withdrawal.amount],
},
}
}
}
impl TryFrom<Withdrawal> for edr_block_header::Withdrawal {
type Error = napi::Error;
fn try_from(value: Withdrawal) -> Result<Self, Self::Error> {
let index: u64 = BigInt::try_cast(value.index)?;
let validator_index: u64 = BigInt::try_cast(value.validator_index)?;
let amount = BigInt::try_cast(value.amount)?;
let address = Address::from_slice(&value.address);
Ok(Self {
index,
validator_index,
address,
amount,
})
}
}