anvil/eth/
sign.rs

1use crate::eth::error::BlockchainError;
2use alloy_consensus::{Sealed, SignableTransaction};
3use alloy_dyn_abi::TypedData;
4use alloy_network::TxSignerSync;
5use alloy_primitives::{Address, B256, Signature, map::AddressHashMap};
6use alloy_signer::Signer as AlloySigner;
7use alloy_signer_local::PrivateKeySigner;
8use foundry_primitives::{FoundryTxEnvelope, FoundryTypedTx};
9
10/// A transaction signer
11#[async_trait::async_trait]
12pub trait Signer: Send + Sync {
13    /// returns the available accounts for this signer
14    fn accounts(&self) -> Vec<Address>;
15
16    /// Returns `true` whether this signer can sign for this address
17    fn is_signer_for(&self, addr: Address) -> bool {
18        self.accounts().contains(&addr)
19    }
20
21    /// Returns the signature
22    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError>;
23
24    /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712
25    /// standard.
26    async fn sign_typed_data(
27        &self,
28        address: Address,
29        payload: &TypedData,
30    ) -> Result<Signature, BlockchainError>;
31
32    /// Signs the given hash.
33    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError>;
34
35    /// signs a transaction request using the given account in request
36    fn sign_transaction(
37        &self,
38        request: FoundryTypedTx,
39        address: &Address,
40    ) -> Result<Signature, BlockchainError>;
41}
42
43/// Maintains developer keys
44pub struct DevSigner {
45    addresses: Vec<Address>,
46    accounts: AddressHashMap<PrivateKeySigner>,
47}
48
49impl DevSigner {
50    pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
51        let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
52        let accounts = addresses.iter().copied().zip(accounts).collect();
53        Self { addresses, accounts }
54    }
55}
56
57#[async_trait::async_trait]
58impl Signer for DevSigner {
59    fn accounts(&self) -> Vec<Address> {
60        self.addresses.clone()
61    }
62
63    fn is_signer_for(&self, addr: Address) -> bool {
64        self.accounts.contains_key(&addr)
65    }
66
67    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
68        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
69
70        Ok(signer.sign_message(message).await?)
71    }
72
73    async fn sign_typed_data(
74        &self,
75        address: Address,
76        payload: &TypedData,
77    ) -> Result<Signature, BlockchainError> {
78        let mut signer =
79            self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
80
81        // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing
82        // typed data.
83        signer.set_chain_id(None);
84
85        Ok(signer.sign_dynamic_typed_data(payload).await?)
86    }
87
88    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
89        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
90
91        Ok(signer.sign_hash(&hash).await?)
92    }
93
94    fn sign_transaction(
95        &self,
96        request: FoundryTypedTx,
97        address: &Address,
98    ) -> Result<Signature, BlockchainError> {
99        let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?;
100        match request {
101            FoundryTypedTx::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
102            FoundryTypedTx::Eip2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
103            FoundryTypedTx::Eip1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
104            FoundryTypedTx::Eip7702(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
105            FoundryTypedTx::Eip4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?),
106            FoundryTypedTx::Deposit(_) => {
107                unreachable!("op deposit txs should not be signed")
108            }
109        }
110    }
111}
112
113/// converts the `request` into a [`FoundryTypedTx`] with the given signature
114///
115/// # Errors
116///
117/// This will fail if the `signature` contains an erroneous recovery id.
118pub fn build_typed_transaction(
119    request: FoundryTypedTx,
120    signature: Signature,
121) -> Result<FoundryTxEnvelope, BlockchainError> {
122    let tx = match request {
123        FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)),
124        FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)),
125        FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)),
126        FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)),
127        FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)),
128        FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)),
129    };
130
131    Ok(tx)
132}