Skip to main content

anvil/eth/
sign.rs

1use crate::eth::error::BlockchainError;
2use alloy_consensus::{Sealed, 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};
9use tempo_primitives::TempoSignature;
10
11/// Network-agnostic signing: messages, typed data, and hashes.
12#[async_trait::async_trait]
13pub trait MessageSigner: Send + Sync {
14    /// returns the available accounts for this signer
15    fn accounts(&self) -> Vec<Address>;
16
17    /// Returns `true` whether this signer can sign for this address
18    fn is_signer_for(&self, addr: Address) -> bool {
19        self.accounts().contains(&addr)
20    }
21
22    /// Returns the signature
23    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError>;
24
25    /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712
26    /// standard.
27    async fn sign_typed_data(
28        &self,
29        address: Address,
30        payload: &TypedData,
31    ) -> Result<Signature, BlockchainError>;
32
33    /// Signs the given hash.
34    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError>;
35}
36
37/// A transaction signer, generic over the network.
38///
39/// Modelled after alloy's `NetworkWallet<N>`: the
40/// [`sign_transaction_from`](Signer::sign_transaction_from) method takes an
41/// unsigned transaction and returns the fully-signed envelope in one step.
42pub trait Signer<N: Network>: MessageSigner {
43    /// Signs an unsigned transaction and returns the signed envelope.
44    ///
45    /// Mirrors `NetworkWallet::sign_transaction_from`.
46    fn sign_transaction_from(
47        &self,
48        sender: &Address,
49        tx: N::UnsignedTx,
50    ) -> Result<N::TxEnvelope, BlockchainError>;
51}
52
53/// Maintains developer keys
54pub struct DevSigner {
55    addresses: Vec<Address>,
56    accounts: AddressHashMap<PrivateKeySigner>,
57}
58
59impl DevSigner {
60    pub fn new(accounts: Vec<PrivateKeySigner>) -> Self {
61        let addresses = accounts.iter().map(|wallet| wallet.address()).collect::<Vec<_>>();
62        let accounts = addresses.iter().copied().zip(accounts).collect();
63        Self { addresses, accounts }
64    }
65}
66
67#[async_trait::async_trait]
68impl MessageSigner for DevSigner {
69    fn accounts(&self) -> Vec<Address> {
70        self.addresses.clone()
71    }
72
73    fn is_signer_for(&self, addr: Address) -> bool {
74        self.accounts.contains_key(&addr)
75    }
76
77    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature, BlockchainError> {
78        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
79
80        Ok(signer.sign_message(message).await?)
81    }
82
83    async fn sign_typed_data(
84        &self,
85        address: Address,
86        payload: &TypedData,
87    ) -> Result<Signature, BlockchainError> {
88        let mut signer =
89            self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned();
90
91        // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing
92        // typed data.
93        signer.set_chain_id(None);
94
95        Ok(signer.sign_dynamic_typed_data(payload).await?)
96    }
97
98    async fn sign_hash(&self, address: Address, hash: B256) -> Result<Signature, BlockchainError> {
99        let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?;
100
101        Ok(signer.sign_hash(&hash).await?)
102    }
103}
104
105impl Signer<foundry_primitives::FoundryNetwork> for DevSigner {
106    fn sign_transaction_from(
107        &self,
108        sender: &Address,
109        tx: FoundryTypedTx,
110    ) -> Result<FoundryTxEnvelope, BlockchainError> {
111        let signer = self.accounts.get(sender).ok_or(BlockchainError::NoSignerAvailable)?;
112        let envelope = match tx {
113            FoundryTypedTx::Legacy(mut t) => {
114                let sig = signer.sign_transaction_sync(&mut t)?;
115                FoundryTxEnvelope::Legacy(t.into_signed(sig))
116            }
117            FoundryTypedTx::Eip2930(mut t) => {
118                let sig = signer.sign_transaction_sync(&mut t)?;
119                FoundryTxEnvelope::Eip2930(t.into_signed(sig))
120            }
121            FoundryTypedTx::Eip1559(mut t) => {
122                let sig = signer.sign_transaction_sync(&mut t)?;
123                FoundryTxEnvelope::Eip1559(t.into_signed(sig))
124            }
125            FoundryTypedTx::Eip7702(mut t) => {
126                let sig = signer.sign_transaction_sync(&mut t)?;
127                FoundryTxEnvelope::Eip7702(t.into_signed(sig))
128            }
129            FoundryTypedTx::Eip4844(mut t) => {
130                let sig = signer.sign_transaction_sync(&mut t)?;
131                FoundryTxEnvelope::Eip4844(t.into_signed(sig))
132            }
133            FoundryTypedTx::Deposit(_) => {
134                unreachable!("op deposit txs should not be signed")
135            }
136            FoundryTypedTx::Tempo(mut t) => {
137                let sig = signer.sign_transaction_sync(&mut t)?;
138                FoundryTxEnvelope::Tempo(t.into_signed(sig.into()))
139            }
140        };
141        Ok(envelope)
142    }
143}
144
145/// Builds a TxEnvelope from UnsignedTx with a zeroed signature.
146///
147/// Used for impersonated accounts, where transactions are accepted without a valid signature.
148pub fn build_impersonated(typed_tx: FoundryTypedTx) -> FoundryTxEnvelope {
149    let signature = Signature::new(Default::default(), Default::default(), false);
150    match typed_tx {
151        FoundryTypedTx::Legacy(tx) => FoundryTxEnvelope::Legacy(tx.into_signed(signature)),
152        FoundryTypedTx::Eip2930(tx) => FoundryTxEnvelope::Eip2930(tx.into_signed(signature)),
153        FoundryTypedTx::Eip1559(tx) => FoundryTxEnvelope::Eip1559(tx.into_signed(signature)),
154        FoundryTypedTx::Eip7702(tx) => FoundryTxEnvelope::Eip7702(tx.into_signed(signature)),
155        FoundryTypedTx::Eip4844(tx) => FoundryTxEnvelope::Eip4844(tx.into_signed(signature)),
156        FoundryTypedTx::Deposit(tx) => FoundryTxEnvelope::Deposit(Sealed::new(tx)),
157        FoundryTypedTx::Tempo(tx) => {
158            let tempo_sig: TempoSignature = signature.into();
159            FoundryTxEnvelope::Tempo(tx.into_signed(tempo_sig))
160        }
161    }
162}