diff --git a/Cargo.lock b/Cargo.lock index 257f63e31f63..d45575ddac63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -932,6 +932,7 @@ dependencies = [ "revm", "serde", "serde_json", + "thiserror", ] [[package]] diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 6ea5e53184da..da337c62d8c8 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -38,6 +38,7 @@ bytes = "1.4" # misc rand = "0.8" +thiserror.workspace = true [features] default = ["serde"] diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index d53473666e1f..e8a3f2ad4f78 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -19,6 +19,7 @@ pub mod subscription; pub mod transaction; pub mod trie; pub mod utils; +pub mod wallet; #[cfg(feature = "serde")] pub mod serde_helpers; @@ -769,6 +770,24 @@ pub enum EthRequest { /// Reorg the chain #[cfg_attr(feature = "serde", serde(rename = "anvil_reorg",))] Reorg(ReorgOptions), + + /// Wallet + #[cfg_attr(feature = "serde", serde(rename = "wallet_getCapabilities", with = "empty_params"))] + WalletGetCapabilities(()), + + /// Wallet send_tx + #[cfg_attr(feature = "serde", serde(rename = "wallet_sendTransaction", with = "sequence"))] + WalletSendTransaction(Box>), + + /// Add an address to the [`DelegationCapability`] of the wallet + /// + /// [`DelegationCapability`]: wallet::DelegationCapability + #[cfg_attr(feature = "serde", serde(rename = "anvil_addCapability", with = "sequence"))] + AnvilAddCapability(Address), + + /// Set the executor (sponsor) wallet + #[cfg_attr(feature = "serde", serde(rename = "anvil_setExecutor", with = "sequence"))] + AnvilSetExecutor(String), } /// Represents ethereum JSON-RPC API diff --git a/crates/anvil/core/src/eth/wallet.rs b/crates/anvil/core/src/eth/wallet.rs new file mode 100644 index 000000000000..8676ec2fbf05 --- /dev/null +++ b/crates/anvil/core/src/eth/wallet.rs @@ -0,0 +1,79 @@ +use alloy_primitives::{map::HashMap, Address, ChainId, U64}; +use serde::{Deserialize, Serialize}; + +/// The capability to perform [EIP-7702][eip-7702] delegations, sponsored by the sequencer. +/// +/// The sequencer will only perform delegations, and act on behalf of delegated accounts, if the +/// account delegates to one of the addresses specified within this capability. +/// +/// [eip-7702]: https://eips.ethereum.org/EIPS/eip-7702 +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct DelegationCapability { + /// A list of valid delegation contracts. + pub addresses: Vec
, +} + +/// Wallet capabilities for a specific chain. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct Capabilities { + /// The capability to delegate. + pub delegation: DelegationCapability, +} + +/// A map of wallet capabilities per chain ID. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct WalletCapabilities(HashMap); + +impl WalletCapabilities { + /// Get the capabilities of the wallet API for the specified chain ID. + pub fn get(&self, chain_id: ChainId) -> Option<&Capabilities> { + self.0.get(&U64::from(chain_id)) + } + + pub fn insert(&mut self, chain_id: ChainId, capabilities: Capabilities) { + self.0.insert(U64::from(chain_id), capabilities); + } +} + +#[derive(Debug, thiserror::Error)] +pub enum WalletError { + /// The transaction value is not 0. + /// + /// The value should be 0 to prevent draining the sequencer. + #[error("tx value not zero")] + ValueNotZero, + /// The from field is set on the transaction. + /// + /// Requests with the from field are rejected, since it is implied that it will always be the + /// sequencer. + #[error("tx from field is set")] + FromSet, + /// The nonce field is set on the transaction. + /// + /// Requests with the nonce field set are rejected, as this is managed by the sequencer. + #[error("tx nonce is set")] + NonceSet, + /// An authorization item was invalid. + /// + /// The item is invalid if it tries to delegate an account to a contract that is not + /// whitelisted. + #[error("invalid authorization address")] + InvalidAuthorization, + /// The to field of the transaction was invalid. + /// + /// The destination is invalid if: + /// + /// - There is no bytecode at the destination, or + /// - The bytecode is not an EIP-7702 delegation designator, or + /// - The delegation designator points to a contract that is not whitelisted + #[error("the destination of the transaction is not a delegated account")] + IllegalDestination, + /// The transaction request was invalid. + /// + /// This is likely an internal error, as most of the request is built by the sequencer. + #[error("invalid tx request")] + InvalidTransactionRequest, + /// An internal error occurred. + #[error("internal error")] + InternalError, +} diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index a85862664684..20ac92234f27 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -4,8 +4,8 @@ use super::{ }; use crate::{ eth::{ - backend, backend::{ + self, db::SerializableState, mem::{MIN_CREATE_GAS, MIN_TRANSACTION_GAS}, notifications::NewBlockNotifications, @@ -23,8 +23,7 @@ use crate::{ }, Pool, }, - sign, - sign::Signer, + sign::{self, Signer}, }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, @@ -34,11 +33,17 @@ use crate::{ use alloy_consensus::{transaction::eip4844::TxEip4844Variant, Account, TxEnvelope}; use alloy_dyn_abi::TypedData; use alloy_eips::eip2718::Encodable2718; -use alloy_network::{eip2718::Decodable2718, BlockResponse}; +use alloy_network::{ + eip2718::Decodable2718, BlockResponse, Ethereum, NetworkWallet, TransactionBuilder, +}; use alloy_primitives::{ map::{HashMap, HashSet}, Address, Bytes, Parity, TxHash, TxKind, B256, B64, U256, U64, }; +use alloy_provider::utils::{ + eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, +}; use alloy_rpc_types::{ anvil::{ ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, @@ -65,6 +70,7 @@ use anvil_core::{ transaction_request_to_typed, PendingTransaction, ReceiptResponse, TypedTransaction, TypedTransactionRequest, }, + wallet::{WalletCapabilities, WalletError}, EthRequest, }, types::{ReorgOptions, TransactionData, Work}, @@ -82,6 +88,7 @@ use foundry_evm::{ }; use futures::channel::{mpsc::Receiver, oneshot}; use parking_lot::RwLock; +use revm::primitives::Bytecode; use std::{future::Future, sync::Arc, time::Duration}; /// The client version: `anvil/v{major}.{minor}.{patch}` @@ -449,6 +456,14 @@ impl EthApi { EthRequest::Reorg(reorg_options) => { self.anvil_reorg(reorg_options).await.to_rpc_result() } + EthRequest::WalletGetCapabilities(()) => self.get_capabilities().to_rpc_result(), + EthRequest::WalletSendTransaction(tx) => { + self.wallet_send_transaction(*tx).await.to_rpc_result() + } + EthRequest::AnvilAddCapability(addr) => self.anvil_add_capability(addr).to_rpc_result(), + EthRequest::AnvilSetExecutor(executor_pk) => { + self.anvil_set_executor(executor_pk).to_rpc_result() + } } } @@ -2369,6 +2384,137 @@ impl EthApi { } } +// ===== impl Wallet endppoints ===== +impl EthApi { + /// Get the capabilities of the wallet. + /// + /// See also [EIP-5792][eip-5792]. + /// + /// [eip-5792]: https://eips.ethereum.org/EIPS/eip-5792 + pub fn get_capabilities(&self) -> Result { + node_info!("wallet_getCapabilities"); + Ok(self.backend.get_capabilities()) + } + + pub async fn wallet_send_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { + node_info!("wallet_sendTransaction"); + + // Validate the request + // reject transactions that have a non-zero value to prevent draining the executor. + if request.value.is_some_and(|val| val > U256::ZERO) { + return Err(WalletError::ValueNotZero.into()) + } + + // reject transactions that have from set, as this will be the executor. + if request.from.is_some() { + return Err(WalletError::FromSet.into()); + } + + // reject transaction requests that have nonce set, as this is managed by the executor. + if request.nonce.is_some() { + return Err(WalletError::NonceSet.into()); + } + + let capabilities = self.backend.get_capabilities(); + let valid_delegations: &[Address] = capabilities + .get(self.chain_id()) + .map(|caps| caps.delegation.addresses.as_ref()) + .unwrap_or_default(); + + if let Some(authorizations) = &request.authorization_list { + if authorizations.iter().any(|auth| !valid_delegations.contains(&auth.address)) { + return Err(WalletError::InvalidAuthorization.into()); + } + } + + // validate the destination address + match (request.authorization_list.is_some(), request.to) { + // if this is an eip-1559 tx, ensure that it is an account that delegates to a + // whitelisted address + (false, Some(TxKind::Call(addr))) => { + let acc = self.backend.get_account(addr).await?; + + let delegated_address = acc + .code + .map(|code| match code { + Bytecode::Eip7702(c) => c.address(), + _ => Address::ZERO, + }) + .unwrap_or_default(); + + // not a whitelisted address, or not an eip-7702 bytecode + if delegated_address == Address::ZERO || + !valid_delegations.contains(&delegated_address) + { + return Err(WalletError::IllegalDestination.into()); + } + } + // if it's an eip-7702 tx, let it through + (true, _) => (), + // create tx's disallowed + _ => return Err(WalletError::IllegalDestination.into()), + } + + let wallet = self.backend.executor_wallet().ok_or(WalletError::InternalError)?; + + let from = NetworkWallet::::default_signer_address(&wallet); + + let nonce = self.get_transaction_count(from, Some(BlockId::latest())).await?; + + request.nonce = Some(nonce); + + let chain_id = self.chain_id(); + + request.chain_id = Some(chain_id); + + request.from = Some(from); + + let gas_limit_fut = self.estimate_gas(request.clone(), Some(BlockId::latest()), None); + + let fees_fut = self.fee_history( + U256::from(EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumber::Latest, + vec![EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ); + + let (gas_limit, fees) = tokio::join!(gas_limit_fut, fees_fut); + + let gas_limit = gas_limit?; + let fees = fees?; + + request.gas = Some(gas_limit.to()); + + let base_fee = fees.latest_block_base_fee().unwrap_or_default(); + + let estimation = eip1559_default_estimator(base_fee, &fees.reward.unwrap_or_default()); + + request.max_fee_per_gas = Some(estimation.max_fee_per_gas); + request.max_priority_fee_per_gas = Some(estimation.max_priority_fee_per_gas); + request.gas_price = None; + + let envelope = request.build(&wallet).await.map_err(|_| WalletError::InternalError)?; + + self.send_raw_transaction(envelope.encoded_2718().into()).await + } + + /// Add an address to the delegation capability of wallet. + /// + /// This entails that the executor will now be able to sponsor transactions to this address. + pub fn anvil_add_capability(&self, address: Address) -> Result<()> { + node_info!("anvil_addCapability"); + self.backend.add_capability(address); + Ok(()) + } + + pub fn anvil_set_executor(&self, executor_pk: String) -> Result
{ + node_info!("anvil_setExecutor"); + self.backend.set_executor(executor_pk) + } +} + impl EthApi { /// Executes the future on a new blocking task. async fn on_blocking_task(&self, c: C) -> Result diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 0b7777f2db20..c707e72cc17c 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -36,7 +36,10 @@ use crate::{ use alloy_chains::NamedChain; use alloy_consensus::{Account, Header, Receipt, ReceiptWithBloom}; use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; -use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256, U64}; +use alloy_network::EthereumWallet; +use alloy_primitives::{ + address, hex, keccak256, utils::Unit, Address, Bytes, TxHash, TxKind, B256, U256, U64, +}; use alloy_rpc_types::{ anvil::Forking, request::TransactionRequest, @@ -56,6 +59,7 @@ use alloy_rpc_types::{ Transaction, TransactionReceipt, }; use alloy_serde::WithOtherFields; +use alloy_signer_local::PrivateKeySigner; use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; use anvil_core::eth::{ block::{Block, BlockInfo}, @@ -64,6 +68,7 @@ use anvil_core::eth::{ TransactionInfo, TypedReceipt, TypedTransaction, }, utils::meets_eip155, + wallet::{Capabilities, DelegationCapability, WalletCapabilities}, }; use anvil_rpc::error::RpcError; use chrono::Datelike; @@ -111,6 +116,17 @@ pub mod storage; pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. pub const MIN_CREATE_GAS: u128 = 53000; +// Executor +pub const EXECUTOR: Address = address!("6634F723546eCc92277e8a2F93d4f248bf1189ea"); +pub const EXECUTOR_PK: &str = "0x502d47e1421cb9abef497096728e69f07543232b93ef24de4998e18b5fd9ba0f"; +// P256 Batch Delegation Contract: https://odyssey-explorer.ithaca.xyz/address/0x35202a6E6317F3CC3a177EeEE562D3BcDA4a6FcC +pub const P256_DELEGATION_CONTRACT: Address = address!("35202a6e6317f3cc3a177eeee562d3bcda4a6fcc"); +// Runtime code of the P256 delegation contract +pub const P256_DELEGATION_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610018575b361561001657005b005b5f3560e01c806309c5eabe146100c75780630cb6aaf1146100c257806330f6a8e5146100bd5780635fce1927146100b8578063641cdfe2146100b357806376ba882d146100ae5780638d80ff0a146100a9578063972ce4bc146100a4578063a78fc2441461009f578063a82e44e01461009a5763b34893910361000e576108e1565b6108b5565b610786565b610646565b6105ba565b610529565b6103f8565b6103a2565b61034c565b6102c0565b61020b565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176100fc57604052565b6100cc565b6080810190811067ffffffffffffffff8211176100fc57604052565b60a0810190811067ffffffffffffffff8211176100fc57604052565b90601f8019910116810190811067ffffffffffffffff8211176100fc57604052565b6040519061016a608083610139565b565b67ffffffffffffffff81116100fc57601f01601f191660200190565b9291926101948261016c565b916101a26040519384610139565b8294818452818301116101be578281602093845f960137010152565b5f80fd5b9080601f830112156101be578160206101dd93359101610188565b90565b60206003198201126101be576004359067ffffffffffffffff82116101be576101dd916004016101c2565b346101be57610219366101e0565b3033036102295761001690610ae6565b636f6a1b8760e11b5f5260045ffd5b634e487b7160e01b5f52603260045260245ffd5b5f54811015610284575f8080526005919091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630191565b610238565b8054821015610284575f52600560205f20910201905f90565b906040516102af816100e0565b602060018294805484520154910152565b346101be5760203660031901126101be576004355f548110156101be576102e69061024c565b5060ff815416600182015491610306600360ff60028401541692016102a2565b926040519215158352602083015260028110156103385760a09260209160408401528051606084015201516080820152f35b634e487b7160e01b5f52602160045260245ffd5b346101be575f3660031901126101be576020600254604051908152f35b6004359063ffffffff821682036101be57565b6064359063ffffffff821682036101be57565b6084359063ffffffff821682036101be57565b346101be5760203660031901126101be576103bb610369565b303303610229576103cb9061024c565b50805460ff19169055005b60609060231901126101be57602490565b60609060831901126101be57608490565b346101be5760803660031901126101be57610411610369565b60205f61041d366103d6565b60015461043161042c82610a0b565b600155565b60405184810191825260e086901b6001600160e01b031916602083015261046581602484015b03601f198101835282610139565b51902060ff61047660408401610a19565b161583146104fe576104b2601b925b85813591013590604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156104f9575f51306001600160a01b03909116036104ea576104df6100169161024c565b50805460ff19169055565b638baa579f60e01b5f5260045ffd5b610a27565b6104b2601c92610485565b60409060031901126101be57600490565b6044359060028210156101be57565b346101be5760803660031901126101be5761054336610509565b61054b61051a565b606435903033036102295761059192610580610587926040519461056e86610101565b60018652602086015260408501610a32565b36906105f3565b6060820152610a3e565b5f545f1981019081116105b55760405163ffffffff919091168152602090f35b0390f35b6109f7565b6100166105c6366101e0565b610ae6565b60409060231901126101be57604051906105e4826100e0565b60243582526044356020830152565b91908260409103126101be5760405161060b816100e0565b6020808294803584520135910152565b6084359081151582036101be57565b60a4359081151582036101be57565b359081151582036101be57565b346101be5760a03660031901126101be5760043567ffffffffffffffff81116101be576106779036906004016101c2565b610680366105cb565b61068861037c565b61069061061b565b906002546106a56106a082610a0b565b600255565b6040516106bb8161045788602083019586610b6a565b51902091610747575b6106d06106d69161024c565b50610b7b565b906106e86106e48351151590565b1590565b610738576020820151801515908161072e575b5061071f576107129260606106e493015191610ce3565b6104ea5761001690610ae6565b632572e3a960e01b5f5260045ffd5b905042115f6106fb565b637dd286d760e11b5f5260045ffd5b905f61077361045761076760209460405192839187830160209181520190565b60405191828092610b58565b039060025afa156104f9575f51906106c4565b346101be5760e03660031901126101be576107a036610509565b6107a861051a565b6064359060205f6107b8366103e7565b6001546107c761042c82610a0b565b60408051808601928352883560208401528589013591830191909152606082018790526107f78160808401610457565b51902060ff61080860408401610a19565b161583146108aa5760408051918252601b602083015282359082015290830135606082015280608081015b838052039060015afa156104f9575f51306001600160a01b03909116036104ea5761087a926105806105879261086761015b565b6001815294602086015260408501610a32565b6105b161089361088a5f54610ad8565b63ffffffff1690565b60405163ffffffff90911681529081906020820190565b610833601c92610485565b346101be575f3660031901126101be576020600154604051908152f35b359061ffff821682036101be57565b346101be5760c03660031901126101be5760043567ffffffffffffffff81116101be576109129036906004016101c2565b61091b366105cb565b906064359167ffffffffffffffff83116101be5760a060031984360301126101be576040516109498161011d565b836004013567ffffffffffffffff81116101be5761096d90600436918701016101c2565b8152602484013567ffffffffffffffff81116101be57840193366023860112156101be5760846109db916109ae610016973690602460048201359101610188565b60208501526109bf604482016108d2565b60408501526109d0606482016108d2565b606085015201610639565b60808201526109e861038f565b916109f161062a565b93610bc3565b634e487b7160e01b5f52601160045260245ffd5b5f1981146105b55760010190565b3560ff811681036101be5790565b6040513d5f823e3d90fd5b60028210156103385752565b5f54680100000000000000008110156100fc57806001610a6192015f555f610289565b610ac557610a7e82511515829060ff801983541691151516179055565b6020820151600182015560028101604083015160028110156103385761016a9360039260609260ff8019835416911617905501519101906020600191805184550151910155565b634e487b7160e01b5f525f60045260245ffd5b5f198101919082116105b557565b80519060205b828110610af857505050565b808201805160f81c600182015160601c91601581015160358201519384915f9493845f14610b4257505050506001146101be575b15610b3a5701605501610aec565b3d5f803e3d5ffd5b5f95508594506055019130811502175af1610b2c565b805191908290602001825e015f815290565b6020906101dd939281520190610b58565b90604051610b8881610101565b6060610bbe6003839560ff8154161515855260018101546020860152610bb860ff60028301541660408701610a32565b016102a2565b910152565b93909192600254610bd66106a082610a0b565b604051610bec8161045789602083019586610b6a565b51902091610c50575b6106d0610c019161024c565b91610c0f6106e48451151590565b6107385760208301518015159081610c46575b5061071f57610c399360606106e494015192610e0d565b6104ea5761016a90610ae6565b905042115f610c22565b905f610c7061045761076760209460405192839187830160209181520190565b039060025afa156104f9575f5190610bf5565b3d15610cad573d90610c948261016c565b91610ca26040519384610139565b82523d5f602084013e565b606090565b8051601f101561028457603f0190565b8051602010156102845760400190565b908151811015610284570160200190565b5f9291839260208251920151906020815191015191604051936020850195865260408501526060840152608083015260a082015260a08152610d2660c082610139565b519060145afa610d34610c83565b81610d74575b81610d43575090565b600160f81b91506001600160f81b031990610d6f90610d6190610cb2565b516001600160f81b03191690565b161490565b80516020149150610d3a565b60405190610d8f604083610139565b6015825274113a3cb832911d113bb2b130baba34371733b2ba1160591b6020830152565b9061016a6001610de3936040519485916c1131b430b63632b733b2911d1160991b6020840152602d830190610b58565b601160f91b815203601e19810185520183610139565b610e069060209392610b58565b9081520190565b92919281516025815110908115610f0a575b50610ef957610e2c610d80565b90610e596106e460208501938451610e53610e4c606089015161ffff1690565b61ffff1690565b91610f9b565b610f01576106e4610e8d610e88610457610e83610ea1956040519283916020830160209181520190565b611012565b610db3565b8351610e53610e4c604088015161ffff1690565b610ef9575f610eb96020925160405191828092610b58565b039060025afa156104f9575f610ee360209261076783519151610457604051938492888401610df9565b039060025afa156104f9576101dd915f51610ce3565b505050505f90565b50505050505f90565b610f2b9150610f1e610d616106e492610cc2565b6080850151151590610f31565b5f610e1f565b906001600160f81b0319600160f81b831601610f955780610f85575b610f8057601f60fb1b600160fb1b821601610f69575b50600190565b600160fc1b90811614610f7c575f610f63565b5f90565b505f90565b50600160fa1b8181161415610f4d565b50505f90565b80519282515f5b858110610fb457505050505050600190565b8083018084116105b5578281101561100757610fe56001600160f81b0319610fdc8488610cd2565b51169187610cd2565b516001600160f81b03191603610ffd57600101610fa2565b5050505050505f90565b505050505050505f90565b80516060929181611021575050565b9092506003600284010460021b604051937f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f603f52602085019282860191602083019460208284010190600460038351955f85525b0191603f8351818160121c16515f538181600c1c1651600153818160061c165160025316516003535f5181520190878210156110db5760049060039061109a565b5095505f93600393604092520160405206600204809303613d3d60f01b81525203825256fea26469706673582212200ba93b78f286a25ece47e9403c47be9862f9b8b70ba1a95098667b90c47308b064736f6c634300081a0033"); +// Experimental ERC20 +pub const EXP_ERC20_CONTRACT: Address = address!("238c8CD93ee9F8c7Edf395548eF60c0d2e46665E"); +// Runtime code of the experimental ERC20 contract +pub const EXP_ERC20_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610010575b005b5f3560e01c806306fdde03146106f7578063095ea7b31461068c57806318160ddd1461066757806323b872dd146105a15780632bb7c5951461050e578063313ce567146104f35780633644e5151461045557806340c10f191461043057806370a08231146103fe5780637ecebe00146103cc57806395d89b4114610366578063a9059cbb146102ea578063ad0c8fdd146102ad578063d505accf146100fb5763dd62ed3e0361000e57346100f75760403660031901126100f7576100d261075c565b6100da610772565b602052637f5e9f20600c525f5260206034600c2054604051908152f35b5f80fd5b346100f75760e03660031901126100f75761011461075c565b61011c610772565b6084359160643560443560ff851685036100f757610138610788565b60208101906e04578706572696d656e74455243323608c1b8252519020908242116102a0576040519360018060a01b03169460018060a01b03169565383775081901600e52855f5260c06020600c20958654957f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252602082019586528660408301967fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc688528b6060850198468a528c608087019330855260a08820602e527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9885252528688525260a082015220604e526042602c205f5260ff1660205260a43560405260c43560605260208060805f60015afa93853d5103610293577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92594602094019055856303faf4f960a51b176040526034602c2055a3005b63ddafbaef5f526004601cfd5b631a15a3cc5f526004601cfd5b5f3660031901126100f7576103e834023481046103e814341517156102d65761000e90336107ac565b634e487b7160e01b5f52601160045260245ffd5b346100f75760403660031901126100f75761030361075c565b602435906387a211a2600c52335f526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c335f51602061080d5f395f51905f52602080a3602060405160018152f35b63f4d678b85f526004601cfd5b346100f7575f3660031901126100f757604051604081019080821067ffffffffffffffff8311176103b8576103b491604052600381526204558560ec1b602082015260405191829182610732565b0390f35b634e487b7160e01b5f52604160045260245ffd5b346100f75760203660031901126100f7576103e561075c565b6338377508600c525f52602080600c2054604051908152f35b346100f75760203660031901126100f75761041761075c565b6387a211a2600c525f52602080600c2054604051908152f35b346100f75760403660031901126100f75761000e61044c61075c565b602435906107ac565b346100f7575f3660031901126100f757602060a0610471610788565b828101906e04578706572696d656e74455243323608c1b8252519020604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252838201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604082015246606082015230608082015220604051908152f35b346100f7575f3660031901126100f757602060405160128152f35b346100f75760203660031901126100f7576004356387a211a2600c52335f526020600c2090815490818111610359575f80806103e88487839688039055806805345cdf77eb68f44c54036805345cdf77eb68f44c5580835282335f51602061080d5f395f51905f52602083a304818115610598575b3390f11561058d57005b6040513d5f823e3d90fd5b506108fc610583565b346100f75760603660031901126100f7576105ba61075c565b6105c2610772565b604435908260601b33602052637f5e9f208117600c526034600c20908154918219610643575b506387a211a2915017600c526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c9060018060a01b03165f51602061080d5f395f51905f52602080a3602060405160018152f35b82851161065a57846387a211a293039055856105e8565b6313be252b5f526004601cfd5b346100f7575f3660031901126100f75760206805345cdf77eb68f44c54604051908152f35b346100f75760403660031901126100f7576106a561075c565b60243590602052637f5e9f20600c52335f52806034600c20555f52602c5160601c337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3602060405160018152f35b346100f7575f3660031901126100f7576103b4610712610788565b6e04578706572696d656e74455243323608c1b6020820152604051918291825b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f757565b602435906001600160a01b03821682036100f757565b604051906040820182811067ffffffffffffffff8211176103b857604052600f8252565b6805345cdf77eb68f44c548281019081106107ff576805345cdf77eb68f44c556387a211a2600c525f526020600c20818154019055602052600c5160601c5f5f51602061080d5f395f51905f52602080a3565b63e5cfe9575f526004601cfdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fbe302881d9891005ba1448ba48547cc1cb17dea1a5c4011dfcb035de325bb1d64736f6c634300081b0033"); pub type State = foundry_evm::utils::StateChangeset; @@ -186,6 +202,9 @@ pub struct Backend { precompile_factory: Option>, /// Prevent race conditions during mining mining: Arc>, + // === wallet === // + capabilities: Arc>, + executor_wallet: Arc>>, } impl Backend { @@ -244,6 +263,43 @@ impl Backend { (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) }; + let (capabilities, executor_wallet) = if alphanet { + // Insert account that sponsors the delegated txs. And deploy P256 delegation contract. + let mut db = db.write().await; + + let _ = db.set_code( + P256_DELEGATION_CONTRACT, + Bytes::from_static(P256_DELEGATION_RUNTIME_CODE), + ); + + // Insert EXP ERC20 contract + let _ = db.set_code(EXP_ERC20_CONTRACT, Bytes::from_static(EXP_ERC20_RUNTIME_CODE)); + + let init_balance = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); // 10K ETH + + // Add ETH + let _ = db.set_balance(EXP_ERC20_CONTRACT, init_balance); + let _ = db.set_balance(EXECUTOR, init_balance); + + let mut capabilities = WalletCapabilities::default(); + + let chain_id = env.read().cfg.chain_id; + capabilities.insert( + chain_id, + Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }, + ); + + let signer: PrivateKeySigner = EXECUTOR_PK.parse().unwrap(); + + let executor_wallet = EthereumWallet::new(signer); + + (capabilities, Some(executor_wallet)) + } else { + (WalletCapabilities::default(), None) + }; + let backend = Self { db, blockchain, @@ -265,6 +321,8 @@ impl Backend { slots_in_an_epoch, precompile_factory, mining: Arc::new(tokio::sync::Mutex::new(())), + capabilities: Arc::new(RwLock::new(capabilities)), + executor_wallet: Arc::new(RwLock::new(executor_wallet)), }; if let Some(interval_block_time) = automine_block_time { @@ -283,11 +341,45 @@ impl Backend { Ok(()) } + /// Get the capabilities of the wallet. + /// + /// Currently the only capability is [`DelegationCapability`]. + /// + /// [`DelegationCapability`]: anvil_core::eth::wallet::DelegationCapability + pub(crate) fn get_capabilities(&self) -> WalletCapabilities { + self.capabilities.read().clone() + } + /// Updates memory limits that should be more strict when auto-mine is enabled pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { self.states.write().update_interval_mine_block_time(block_time) } + pub(crate) fn executor_wallet(&self) -> Option { + self.executor_wallet.read().clone() + } + + /// Adds an address to the [`DelegationCapability`] of the wallet. + pub(crate) fn add_capability(&self, address: Address) { + let chain_id = self.env.read().cfg.chain_id; + let mut capabilities = self.capabilities.write(); + let mut capability = capabilities.get(chain_id).cloned().unwrap_or_default(); + capability.delegation.addresses.push(address); + capabilities.insert(chain_id, capability); + } + + pub(crate) fn set_executor(&self, executor_pk: String) -> Result { + let signer: PrivateKeySigner = + executor_pk.parse().map_err(|_| RpcError::invalid_params("Invalid private key"))?; + + let executor = signer.address(); + let wallet = EthereumWallet::new(signer); + + *self.executor_wallet.write() = Some(wallet); + + Ok(executor) + } + /// Applies the configured genesis settings /// /// This will fund, create the genesis accounts diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 31d0521bb854..7af513ff1280 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -5,6 +5,7 @@ use alloy_primitives::{Bytes, SignatureError}; use alloy_rpc_types::BlockNumberOrTag; use alloy_signer::Error as SignerError; use alloy_transport::TransportError; +use anvil_core::eth::wallet::WalletError; use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, @@ -119,6 +120,26 @@ where } } +impl From for BlockchainError { + fn from(value: WalletError) -> Self { + match value { + WalletError::ValueNotZero => Self::Message("tx value not zero".to_string()), + WalletError::FromSet => Self::Message("tx from field is set".to_string()), + WalletError::NonceSet => Self::Message("tx nonce is set".to_string()), + WalletError::InvalidAuthorization => { + Self::Message("invalid authorization address".to_string()) + } + WalletError::IllegalDestination => Self::Message( + "the destination of the transaction is not a delegated account".to_string(), + ), + WalletError::InternalError => Self::Message("internal error".to_string()), + WalletError::InvalidTransactionRequest => { + Self::Message("invalid tx request".to_string()) + } + } + } +} + /// Errors that can occur in the transaction pool #[derive(Debug, thiserror::Error)] pub enum PoolError { diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 74728f94b593..088eb76feae8 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -7,7 +7,7 @@ use crate::{ }; use alloy_consensus::{SignableTransaction, TxEip1559}; use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; -use alloy_primitives::{address, fixed_bytes, Address, Bytes, TxKind, U256}; +use alloy_primitives::{address, fixed_bytes, utils::Unit, Address, Bytes, TxKind, U256}; use alloy_provider::{ext::TxPoolApi, Provider}; use alloy_rpc_types::{ anvil::{ @@ -16,9 +16,18 @@ use alloy_rpc_types::{ BlockId, BlockNumberOrTag, TransactionRequest, }; use alloy_serde::WithOtherFields; -use anvil::{eth::api::CLIENT_VERSION, spawn, EthereumHardfork, NodeConfig}; +use anvil::{ + eth::{ + api::CLIENT_VERSION, + backend::mem::{EXECUTOR, P256_DELEGATION_CONTRACT, P256_DELEGATION_RUNTIME_CODE}, + }, + spawn, EthereumHardfork, NodeConfig, +}; use anvil_core::{ - eth::EthRequest, + eth::{ + wallet::{Capabilities, DelegationCapability, WalletCapabilities}, + EthRequest, + }, types::{ReorgOptions, TransactionData}, }; use foundry_evm::revm::primitives::SpecId; @@ -793,3 +802,72 @@ async fn test_reorg() { .await; assert!(res.is_err()); } + +// === wallet endpoints === // +#[tokio::test(flavor = "multi_thread")] +async fn can_get_wallet_capabilities() { + let (api, handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + + let provider = handle.http_provider(); + + let init_sponsor_bal = provider.get_balance(EXECUTOR).await.unwrap(); + + let expected_bal = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); + assert_eq!(init_sponsor_bal, expected_bal); + + let p256_code = provider.get_code_at(P256_DELEGATION_CONTRACT).await.unwrap(); + + assert_eq!(p256_code, Bytes::from_static(P256_DELEGATION_RUNTIME_CODE)); + + let capabilities = api.get_capabilities().unwrap(); + + let mut expect_caps = WalletCapabilities::default(); + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(capabilities, expect_caps); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_add_capability() { + let (api, _handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + + let init_capabilities = api.get_capabilities().unwrap(); + + let mut expect_caps = WalletCapabilities::default(); + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(init_capabilities, expect_caps); + + let new_cap_addr = Address::with_last_byte(1); + + api.anvil_add_capability(new_cap_addr).unwrap(); + + let capabilities = api.get_capabilities().unwrap(); + + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { + addresses: vec![P256_DELEGATION_CONTRACT, new_cap_addr], + }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(capabilities, expect_caps); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_set_executor() { + let (api, _handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + + let expected_addr = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + + let executor = api.anvil_set_executor(pk).unwrap(); + + assert_eq!(executor, expected_addr); +}