Skip to main content

anvil/eth/
sign.rs

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