Skip to main content

anvil/eth/
sign.rs

1use crate::eth::error::BlockchainError;
2use alloy_consensus::SignableTransaction;
3use alloy_dyn_abi::TypedData;
4use alloy_network::{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/// Network-agnostic signing: messages, typed data, and hashes.
11#[async_trait::async_trait]
12pub trait MessageSigner: 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
36/// A transaction signer, generic over the network.
37///
38/// Modelled after alloy's `NetworkWallet<N>`: the
39/// [`sign_transaction_from`](Signer::sign_transaction_from) method takes an
40/// unsigned transaction and returns the fully-signed envelope in one step.
41pub trait Signer<N: Network>: MessageSigner {
42    /// Signs an unsigned transaction and returns the signed envelope.
43    ///
44    /// Mirrors `NetworkWallet::sign_transaction_from`.
45    fn sign_transaction_from(
46        &self,
47        sender: &Address,
48        tx: N::UnsignedTx,
49    ) -> Result<N::TxEnvelope, BlockchainError>;
50}
51
52/// Maintains developer keys
53pub struct DevSigner {
54    addresses: Vec<Address>,
55    accounts: AddressHashMap<PrivateKeySigner>,
56}
57
58impl DevSigner {
59    pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
60        let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
61        let accounts = addresses.iter().copied().zip(accounts).collect();
62        Self { addresses, accounts }
63    }
64}
65
66#[async_trait::async_trait]
67impl MessageSigner for DevSigner {
68    fn accounts(&self) -> Vec<Address> {
69        self.addresses.clone()
70    }
71
72    fn is_signer_for(&self, addr: Address) -> bool {
73        self.accounts.contains_key(&addr)
74    }
75
76    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
77        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
78
79        Ok(signer.sign_message(message).await?)
80    }
81
82    async fn sign_typed_data(
83        &self,
84        address: Address,
85        payload: &TypedData,
86    ) -> Result<Signature, BlockchainError> {
87        let mut signer =
88            self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
89
90        // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing
91        // typed data.
92        signer.set_chain_id(None);
93
94        Ok(signer.sign_dynamic_typed_data(payload).await?)
95    }
96
97    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
98        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
99
100        Ok(signer.sign_hash(&hash).await?)
101    }
102}
103
104impl Signer<foundry_primitives::FoundryNetwork> for DevSigner {
105    fn sign_transaction_from(
106        &self,
107        sender: &Address,
108        tx: FoundryTypedTx,
109    ) -> Result<FoundryTxEnvelope, BlockchainError> {
110        let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?;
111        let envelope = match tx {
112            FoundryTypedTx::Legacy(mut t) => {
113                let sig = signer.sign_transaction_sync(&mut t)?;
114                FoundryTxEnvelope::Legacy(t.into_signed(sig))
115            }
116            FoundryTypedTx::Eip2930(mut t) => {
117                let sig = signer.sign_transaction_sync(&mut t)?;
118                FoundryTxEnvelope::Eip2930(t.into_signed(sig))
119            }
120            FoundryTypedTx::Eip1559(mut t) => {
121                let sig = signer.sign_transaction_sync(&mut t)?;
122                FoundryTxEnvelope::Eip1559(t.into_signed(sig))
123            }
124            FoundryTypedTx::Eip7702(mut t) => {
125                let sig = signer.sign_transaction_sync(&mut t)?;
126                FoundryTxEnvelope::Eip7702(t.into_signed(sig))
127            }
128            FoundryTypedTx::Eip4844(mut t) => {
129                let sig = signer.sign_transaction_sync(&mut t)?;
130                FoundryTxEnvelope::Eip4844(t.into_signed(sig))
131            }
132            #[cfg(feature = "optimism")]
133            FoundryTypedTx::Deposit(_) => {
134                unreachable!("op deposit txs should not be signed")
135            }
136            #[cfg(feature = "optimism")]
137            FoundryTypedTx::PostExec(_) => {
138                unreachable!("op post-exec txs should not be signed")
139            }
140            FoundryTypedTx::Tempo(mut t) => {
141                let sig = signer.sign_transaction_sync(&mut t)?;
142                FoundryTxEnvelope::Tempo(t.into_signed(sig.into()))
143            }
144        };
145        Ok(envelope)
146    }
147}