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:
124
dev/env/node_modules/@nomicfoundation/edr/src/account.rs
generated
vendored
Executable file
124
dev/env/node_modules/@nomicfoundation/edr/src/account.rs
generated
vendored
Executable 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
28
dev/env/node_modules/@nomicfoundation/edr/src/block.rs
generated
vendored
Executable 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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
116
dev/env/node_modules/@nomicfoundation/edr/src/call_override.rs
generated
vendored
Executable file
116
dev/env/node_modules/@nomicfoundation/edr/src/call_override.rs
generated
vendored
Executable 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
165
dev/env/node_modules/@nomicfoundation/edr/src/cast.rs
generated
vendored
Executable 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
7
dev/env/node_modules/@nomicfoundation/edr/src/chains.rs
generated
vendored
Executable 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;
|
||||
58
dev/env/node_modules/@nomicfoundation/edr/src/chains/generic.rs
generated
vendored
Executable file
58
dev/env/node_modules/@nomicfoundation/edr/src/chains/generic.rs
generated
vendored
Executable 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
274
dev/env/node_modules/@nomicfoundation/edr/src/chains/l1.rs
generated
vendored
Executable 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
450
dev/env/node_modules/@nomicfoundation/edr/src/chains/op.rs
generated
vendored
Executable 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
710
dev/env/node_modules/@nomicfoundation/edr/src/config.rs
generated
vendored
Executable 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
447
dev/env/node_modules/@nomicfoundation/edr/src/context.rs
generated
vendored
Executable 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",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
57
dev/env/node_modules/@nomicfoundation/edr/src/contract_decoder.rs
generated
vendored
Executable file
57
dev/env/node_modules/@nomicfoundation/edr/src/contract_decoder.rs
generated
vendored
Executable 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
40
dev/env/node_modules/@nomicfoundation/edr/src/debug_trace.rs
generated
vendored
Executable 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
92
dev/env/node_modules/@nomicfoundation/edr/src/gas_report.rs
generated
vendored
Executable 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
115
dev/env/node_modules/@nomicfoundation/edr/src/instrument.rs
generated
vendored
Executable 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
50
dev/env/node_modules/@nomicfoundation/edr/src/lib.rs
generated
vendored
Executable 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
28
dev/env/node_modules/@nomicfoundation/edr/src/log.rs
generated
vendored
Executable 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
120
dev/env/node_modules/@nomicfoundation/edr/src/logger.rs
generated
vendored
Executable 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
71
dev/env/node_modules/@nomicfoundation/edr/src/mock.rs
generated
vendored
Executable 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
134
dev/env/node_modules/@nomicfoundation/edr/src/mock/time.rs
generated
vendored
Executable 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
50
dev/env/node_modules/@nomicfoundation/edr/src/precompile.rs
generated
vendored
Executable 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
162
dev/env/node_modules/@nomicfoundation/edr/src/provider.rs
generated
vendored
Executable 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()))
|
||||
}
|
||||
}
|
||||
22
dev/env/node_modules/@nomicfoundation/edr/src/provider/factory.rs
generated
vendored
Executable file
22
dev/env/node_modules/@nomicfoundation/edr/src/provider/factory.rs
generated
vendored
Executable 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 }
|
||||
}
|
||||
}
|
||||
73
dev/env/node_modules/@nomicfoundation/edr/src/provider/response.rs
generated
vendored
Executable file
73
dev/env/node_modules/@nomicfoundation/edr/src/provider/response.rs
generated
vendored
Executable 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
216
dev/env/node_modules/@nomicfoundation/edr/src/result.rs
generated
vendored
Executable 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
53
dev/env/node_modules/@nomicfoundation/edr/src/scenarios.rs
generated
vendored
Executable 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
57
dev/env/node_modules/@nomicfoundation/edr/src/serde.rs
generated
vendored
Executable 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(),
|
||||
}
|
||||
}
|
||||
57
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests.rs
generated
vendored
Executable file
57
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests.rs
generated
vendored
Executable 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
184
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/artifact.rs
generated
vendored
Executable file
184
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/artifact.rs
generated
vendored
Executable 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
37
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/cheatcode_errors.rs
generated
vendored
Executable file
37
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/cheatcode_errors.rs
generated
vendored
Executable 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
984
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/config.rs
generated
vendored
Executable file
984
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/config.rs
generated
vendored
Executable 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/factory.rs
generated
vendored
Executable file
22
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/factory.rs
generated
vendored
Executable 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 }
|
||||
}
|
||||
}
|
||||
72
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/l1.rs
generated
vendored
Executable file
72
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/l1.rs
generated
vendored
Executable 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()
|
||||
}
|
||||
72
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/op.rs
generated
vendored
Executable file
72
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/op.rs
generated
vendored
Executable 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()
|
||||
}
|
||||
51
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/runner.rs
generated
vendored
Executable file
51
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/runner.rs
generated
vendored
Executable 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))
|
||||
}
|
||||
}
|
||||
790
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/test_results.rs
generated
vendored
Executable file
790
dev/env/node_modules/@nomicfoundation/edr/src/solidity_tests/test_results.rs
generated
vendored
Executable 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
32
dev/env/node_modules/@nomicfoundation/edr/src/subscription.rs
generated
vendored
Executable file
32
dev/env/node_modules/@nomicfoundation/edr/src/subscription.rs
generated
vendored
Executable 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
199
dev/env/node_modules/@nomicfoundation/edr/src/trace.rs
generated
vendored
Executable 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
61
dev/env/node_modules/@nomicfoundation/edr/src/trace/debug.rs
generated
vendored
Executable 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
89
dev/env/node_modules/@nomicfoundation/edr/src/trace/exit.rs
generated
vendored
Executable 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()
|
||||
}
|
||||
}
|
||||
11
dev/env/node_modules/@nomicfoundation/edr/src/trace/library_utils.rs
generated
vendored
Executable file
11
dev/env/node_modules/@nomicfoundation/edr/src/trace/library_utils.rs
generated
vendored
Executable 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
59
dev/env/node_modules/@nomicfoundation/edr/src/trace/model.rs
generated
vendored
Executable 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
96
dev/env/node_modules/@nomicfoundation/edr/src/trace/return_data.rs
generated
vendored
Executable file
96
dev/env/node_modules/@nomicfoundation/edr/src/trace/return_data.rs
generated
vendored
Executable 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
883
dev/env/node_modules/@nomicfoundation/edr/src/trace/solidity_stack_trace.rs
generated
vendored
Executable file
883
dev/env/node_modules/@nomicfoundation/edr/src/trace/solidity_stack_trace.rs
generated
vendored
Executable 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)
|
||||
}
|
||||
46
dev/env/node_modules/@nomicfoundation/edr/src/ts/solidity_tests.ts
generated
vendored
Executable file
46
dev/env/node_modules/@nomicfoundation/edr/src/ts/solidity_tests.ts
generated
vendored
Executable 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
49
dev/env/node_modules/@nomicfoundation/edr/src/withdrawal.rs
generated
vendored
Executable 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user