Skip to main content

avalanche_types/evm/eip1559/
mod.rs

1//! EIP-1559 transaction type.
2use std::io::{self, Error, ErrorKind};
3
4use ethers::prelude::Eip1559TransactionRequest;
5use ethers_core::types::{transaction::eip2718::TypedTransaction, RecoveryMessage, Signature};
6use primitive_types::{H160, H256, U256};
7
8/// Transaction but without provider.
9#[derive(Clone, Debug)]
10pub struct Transaction {
11    pub chain_id: u64,
12    pub signer_nonce: Option<U256>,
13    pub max_priority_fee_per_gas: Option<U256>,
14    pub max_fee_per_gas: Option<U256>,
15    pub gas_limit: Option<U256>,
16
17    // "from" itself is not RLP-encoded field
18    // "from" can be simply derived from signature and transaction hash
19    // when the RPC decodes the raw transaction
20    pub from: H160,
21    pub recipient: Option<H160>,
22
23    pub value: Option<U256>,
24    pub data: Option<Vec<u8>>,
25}
26
27impl Transaction {
28    pub fn new() -> Self {
29        Self {
30            chain_id: 0,
31            signer_nonce: None,
32
33            max_priority_fee_per_gas: None,
34            max_fee_per_gas: None,
35            gas_limit: None,
36
37            from: H160::zero(),
38            recipient: None,
39            value: None,
40            data: None,
41        }
42    }
43
44    #[must_use]
45    pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
46        self.chain_id = chain_id.into();
47        self
48    }
49
50    #[must_use]
51    pub fn signer_nonce(mut self, signer_nonce: impl Into<U256>) -> Self {
52        self.signer_nonce = Some(signer_nonce.into());
53        self
54    }
55
56    #[must_use]
57    pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: impl Into<U256>) -> Self {
58        self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas.into());
59        self
60    }
61
62    #[must_use]
63    pub fn max_fee_per_gas(mut self, max_fee_per_gas: impl Into<U256>) -> Self {
64        self.max_fee_per_gas = Some(max_fee_per_gas.into());
65        self
66    }
67
68    #[must_use]
69    pub fn gas_limit(mut self, gas_limit: impl Into<U256>) -> Self {
70        self.gas_limit = Some(gas_limit.into());
71        self
72    }
73
74    #[must_use]
75    pub fn from(mut self, from: impl Into<H160>) -> Self {
76        self.from = from.into();
77        self
78    }
79
80    #[must_use]
81    pub fn recipient(mut self, to: impl Into<H160>) -> Self {
82        self.recipient = Some(to.into());
83        self
84    }
85
86    #[must_use]
87    pub fn value(mut self, value: impl Into<U256>) -> Self {
88        self.value = Some(value.into());
89        self
90    }
91
92    #[must_use]
93    pub fn data(mut self, data: impl Into<Vec<u8>>) -> Self {
94        self.data = Some(data.into());
95        self
96    }
97
98    /// Signs the transaction as "ethers_core::types::transaction::eip2718::TypedTransaction"
99    /// and returns the rlp-encoded bytes that can be sent via "eth_sendRawTransaction".
100    /// ref. <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction>
101    pub async fn sign_as_typed_transaction(
102        &self,
103        eth_signer: impl ethers_signers::Signer + Clone,
104    ) -> io::Result<ethers_core::types::Bytes> {
105        let mut tx_request = Eip1559TransactionRequest::new()
106            .from(ethers::prelude::H160::from(self.from.as_fixed_bytes()))
107            .chain_id(ethers::prelude::U64::from(self.chain_id));
108
109        if let Some(signer_nonce) = self.signer_nonce {
110            tx_request = tx_request.nonce(signer_nonce);
111        }
112
113        if let Some(to) = &self.recipient {
114            tx_request = tx_request.to(ethers::prelude::H160::from(to.as_fixed_bytes()));
115        }
116
117        if let Some(value) = &self.value {
118            let converted: ethers::prelude::U256 = value.into();
119            tx_request = tx_request.value(converted);
120        }
121
122        if let Some(max_priority_fee_per_gas) = &self.max_priority_fee_per_gas {
123            let converted: ethers::prelude::U256 = max_priority_fee_per_gas.into();
124            tx_request = tx_request.max_priority_fee_per_gas(converted);
125        }
126
127        if let Some(max_fee_per_gas) = &self.max_fee_per_gas {
128            let converted: ethers::prelude::U256 = max_fee_per_gas.into();
129            tx_request = tx_request.max_fee_per_gas(converted);
130        }
131
132        if let Some(gas_limit) = &self.gas_limit {
133            let converted: ethers::prelude::U256 = gas_limit.into();
134            tx_request = tx_request.gas(converted);
135        }
136
137        if let Some(data) = &self.data {
138            tx_request = tx_request.data(data.clone());
139        }
140
141        let tx: TypedTransaction = tx_request.into();
142        let sig = eth_signer.sign_transaction(&tx).await.map_err(|e| {
143            Error::new(
144                ErrorKind::Other,
145                format!("failed to sign_transaction '{}'", e),
146            )
147        })?;
148
149        Ok(tx.rlp_signed(&sig))
150    }
151}
152
153impl Default for Transaction {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159/// Decodes the RLP-encoded signed "ethers_core::types::transaction::eip2718::TypedTransaction" bytes.
160/// ref. <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction>
161pub fn decode_signed_rlp(b: impl AsRef<[u8]>) -> io::Result<(TypedTransaction, Signature)> {
162    let r = rlp::Rlp::new(b.as_ref());
163    TypedTransaction::decode_signed(&r)
164        .map_err(|e| Error::new(ErrorKind::Other, format!("failed decode_signed '{}'", e)))
165}
166
167/// Decodes the RLP-encoded signed "ethers_core::types::transaction::eip2718::TypedTransaction" bytes.
168/// And verifies the decoded signature.
169/// It returns the typed transaction, transaction hash, its signer address, and the signature.
170/// ref. <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction>
171pub fn decode_and_verify_signed_rlp(
172    b: impl AsRef<[u8]>,
173) -> io::Result<(TypedTransaction, H256, H160, Signature)> {
174    let r = rlp::Rlp::new(b.as_ref());
175    let (decoded_tx, sig) = TypedTransaction::decode_signed(&r)
176        .map_err(|e| Error::new(ErrorKind::Other, format!("failed decode_signed '{}'", e)))?;
177
178    let tx_hash = decoded_tx.sighash();
179    log::debug!("decoded signed transaction hash: 0x{:x}", tx_hash);
180
181    let signer_addr = sig.recover(RecoveryMessage::Hash(tx_hash)).map_err(|e| {
182        Error::new(
183            ErrorKind::Other,
184            format!(
185                "failed to recover signer address from signature and signed transaction hash '{}'",
186                e
187            ),
188        )
189    })?;
190
191    sig.verify(RecoveryMessage::Hash(tx_hash), signer_addr)
192        .map_err(|e| {
193            Error::new(
194                ErrorKind::Other,
195                format!(
196                    "failed to verify signature against the signed transaction hash '{}'",
197                    e
198                ),
199            )
200        })?;
201    log::info!(
202        "verified signer address '{}' against signature and transaction hash",
203        signer_addr
204    );
205
206    Ok((decoded_tx, tx_hash, signer_addr, sig))
207}
208
209/// RUST_LOG=debug cargo test --package avalanche-types --lib --features="evm" -- evm::eip1559::test_transaction --exact --show-output
210#[test]
211fn test_transaction() {
212    let _ = env_logger::builder()
213        .filter_level(log::LevelFilter::Debug)
214        .is_test(true)
215        .try_init();
216
217    macro_rules! ab {
218        ($e:expr) => {
219            tokio_test::block_on($e)
220        };
221    }
222
223    let k1 = crate::key::secp256k1::private_key::Key::generate().unwrap();
224    let key_info1 = k1.to_info(1234).unwrap();
225    log::info!("created {}", key_info1.h160_address);
226    let k1_signer: ethers_signers::LocalWallet = k1.to_ethers_core_signing_key().into();
227
228    let k2 = crate::key::secp256k1::private_key::Key::generate().unwrap();
229    let key_info2 = k2.to_info(1234).unwrap();
230    log::info!("created {}", key_info2.h160_address);
231
232    let chain_id = random_manager::u64() % 3000;
233    let signer_nonce = U256::from(random_manager::u64() % 10);
234    let gas_limit = U256::from(random_manager::u64() % 10000);
235    let max_fee_per_gas = U256::from(random_manager::u64() % 10000);
236    let value = U256::from(random_manager::u64() % 100000);
237
238    let tx = Transaction::new()
239        .chain_id(chain_id)
240        .from(key_info1.h160_address)
241        .recipient(key_info2.h160_address)
242        .signer_nonce(signer_nonce)
243        .max_fee_per_gas(max_fee_per_gas)
244        .gas_limit(gas_limit)
245        .value(value);
246
247    let signed_bytes = ab!(tx.sign_as_typed_transaction(k1_signer)).unwrap();
248    log::info!("signed_bytes: {}", signed_bytes);
249
250    let (decoded_tx, sig) = decode_signed_rlp(&signed_bytes).unwrap();
251    let (decoded_tx2, _tx_hash, signer_addr, sig2) =
252        decode_and_verify_signed_rlp(&signed_bytes).unwrap();
253
254    assert_eq!(decoded_tx, decoded_tx2);
255    assert_eq!(sig, sig2);
256    assert_eq!(decoded_tx.chain_id().unwrap().as_u64(), chain_id);
257    assert_eq!(*decoded_tx.from().unwrap(), key_info1.h160_address);
258    assert_eq!(signer_addr, key_info1.h160_address);
259    assert_eq!(*decoded_tx.to_addr().unwrap(), key_info2.h160_address);
260    assert_eq!(decoded_tx.nonce().unwrap().as_u64(), signer_nonce.as_u64());
261    assert_eq!(decoded_tx.gas().unwrap().as_u64(), gas_limit.as_u64());
262    assert_eq!(decoded_tx.value().unwrap().as_u64(), value.as_u64());
263}