From 830759c819aaf9ee660fca4de83f317efca5e0e6 Mon Sep 17 00:00:00 2001 From: aya015757881 <2581015450@qq.com> Date: Wed, 31 Jul 2024 18:07:19 +0800 Subject: [PATCH] feat: eip 1559 --- Cargo.lock | 2 +- anychain-ethereum/Cargo.toml | 2 +- anychain-ethereum/src/amount.rs | 434 -------------- anychain-ethereum/src/lib.rs | 30 +- anychain-ethereum/src/transaction.rs | 822 +++++++++++++++++---------- anychain-ethereum/src/util.rs | 57 ++ 6 files changed, 574 insertions(+), 773 deletions(-) delete mode 100644 anychain-ethereum/src/amount.rs create mode 100644 anychain-ethereum/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 78f6c34..35ad202 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,7 +188,7 @@ dependencies = [ [[package]] name = "anychain-ethereum" -version = "0.1.14" +version = "0.1.15" dependencies = [ "anychain-core", "ethabi", diff --git a/anychain-ethereum/Cargo.toml b/anychain-ethereum/Cargo.toml index f8dab5e..3c12ae3 100644 --- a/anychain-ethereum/Cargo.toml +++ b/anychain-ethereum/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "anychain-ethereum" description = "A Rust library for Ethereum-focused cryptocurrency wallets, enabling seamless transactions on the Ethereum blockchain" -version = "0.1.14" +version = "0.1.15" keywords = ["blockchain", "crypto", "cryptocurrencies", "ethereum", "wallet"] # Workspace inherited keys diff --git a/anychain-ethereum/src/amount.rs b/anychain-ethereum/src/amount.rs deleted file mode 100644 index d3a6831..0000000 --- a/anychain-ethereum/src/amount.rs +++ /dev/null @@ -1,434 +0,0 @@ -use anychain_core::{to_basic_unit as to_wei, Amount, AmountError}; - -use core::fmt; -use ethereum_types::U256; -use serde::{Deserialize, Serialize}; -use std::ops::{Add, Sub}; - -/// Represents the amount of Ethereum in wei -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct EthereumAmount(pub U256); - -pub enum Denomination { - Wei, - Kwei, - Mwei, - Gwei, - Szabo, - Finney, - Ether, -} - -impl Denomination { - /// The number of decimal places more than a wei. - fn precision(self) -> u32 { - match self { - Denomination::Wei => 0, - Denomination::Kwei => 3, - Denomination::Mwei => 6, - Denomination::Gwei => 9, - Denomination::Szabo => 12, - Denomination::Finney => 15, - Denomination::Ether => 18, - } - } -} - -impl fmt::Display for Denomination { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match self { - Denomination::Wei => "wei", - Denomination::Kwei => "kwei", - Denomination::Mwei => "mwei", - Denomination::Gwei => "gwei", - Denomination::Szabo => "szabo", - Denomination::Finney => "finney", - Denomination::Ether => "ETH", - } - ) - } -} - -impl Amount for EthereumAmount {} - -impl EthereumAmount { - pub fn u256_from_str(val: &str) -> Result { - match U256::from_dec_str(val) { - Ok(wei) => Ok(wei), - Err(error) => Err(AmountError::Crate("uint", format!("{:?}", error))), - } - } - - pub fn from_u256(wei: U256) -> Self { - Self(wei) - } - - pub fn from_wei(wei_value: &str) -> Result { - let wei = Self::u256_from_str(wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_kwei(kwei_value: &str) -> Result { - let wei_value = to_wei(kwei_value, Denomination::Kwei.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_mwei(mwei_value: &str) -> Result { - let wei_value = to_wei(mwei_value, Denomination::Mwei.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_gwei(gwei_value: &str) -> Result { - let wei_value = to_wei(gwei_value, Denomination::Gwei.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_szabo(szabo_value: &str) -> Result { - let wei_value = to_wei(szabo_value, Denomination::Szabo.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_finney(finney_value: &str) -> Result { - let wei_value = to_wei(finney_value, Denomination::Finney.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } - - pub fn from_eth(eth_value: &str) -> Result { - let wei_value = to_wei(eth_value, Denomination::Ether.precision()); - let wei = Self::u256_from_str(&wei_value)?; - - Ok(Self::from_u256(wei)) - } -} - -impl Add for EthereumAmount { - type Output = Self; - fn add(self, rhs: Self) -> Self { - Self::from_u256(self.0 + rhs.0) - } -} - -impl Sub for EthereumAmount { - type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - Self::from_u256(self.0 - rhs.0) - } -} - -impl fmt::Display for EthereumAmount { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn test_from_wei(wei_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_wei(wei_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_finney(finney_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_finney(finney_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_szabo(szabo_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_szabo(szabo_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_gwei(gwei_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_gwei(gwei_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_mwei(mwei_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_mwei(mwei_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_kwei(kwei_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_kwei(kwei_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_from_eth(eth_value: &str, expected_amount: &str) { - let amount = EthereumAmount::from_eth(eth_value).unwrap(); - assert_eq!(expected_amount, amount.to_string()) - } - - fn test_addition(a: &str, b: &str, result: &str) { - let a = EthereumAmount::from_wei(a).unwrap(); - let b = EthereumAmount::from_wei(b).unwrap(); - let result = EthereumAmount::from_wei(result).unwrap(); - - assert_eq!(result, a.add(b)); - } - - fn test_subtraction(a: &str, b: &str, result: &str) { - let a = EthereumAmount::from_wei(a).unwrap(); - let b = EthereumAmount::from_wei(b).unwrap(); - let result = EthereumAmount::from_wei(result).unwrap(); - - assert_eq!(result, a.sub(b)); - } - - pub struct AmountDenominationTestCase { - wei: &'static str, - kwei: &'static str, - mwei: &'static str, - gwei: &'static str, - szabo: &'static str, - finney: &'static str, - ether: &'static str, - } - - mod valid_conversions { - use super::*; - - const TEST_AMOUNTS: [AmountDenominationTestCase; 5] = [ - AmountDenominationTestCase { - wei: "0", - kwei: "0", - mwei: "0", - gwei: "0", - szabo: "0", - finney: "0", - ether: "0", - }, - AmountDenominationTestCase { - wei: "1000000000000000000", - kwei: "1000000000000000", - mwei: "1000000000000", - gwei: "1000000000", - szabo: "1000000", - finney: "1000", - ether: "1", - }, - AmountDenominationTestCase { - wei: "1000000000000000000000", - kwei: "1000000000000000000", - mwei: "1000000000000000", - gwei: "1000000000000", - szabo: "1000000000", - finney: "1000000", - ether: "1000", - }, - AmountDenominationTestCase { - wei: "1234567000000000000000000", - kwei: "1234567000000000000000", - mwei: "1234567000000000000", - gwei: "1234567000000000", - szabo: "1234567000000", - finney: "1234567000", - ether: "1234567", - }, - AmountDenominationTestCase { - wei: "100000000000000000000000000", - kwei: "100000000000000000000000", - mwei: "100000000000000000000", - gwei: "100000000000000000", - szabo: "100000000000000", - finney: "100000000000", - ether: "100000000", - }, - ]; - - #[test] - fn test_wei_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_wei(amounts.wei, amounts.wei)); - } - - #[test] - fn test_finney_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_finney(amounts.finney, amounts.wei)); - } - - #[test] - fn test_szabo_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_szabo(amounts.szabo, amounts.wei)); - } - - #[test] - fn test_gwei_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_gwei(amounts.gwei, amounts.wei)); - } - - #[test] - fn test_mwei_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_mwei(amounts.mwei, amounts.wei)); - } - - #[test] - fn test_kwei_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_kwei(amounts.kwei, amounts.wei)); - } - - #[test] - fn test_eth_conversion() { - TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_eth(amounts.ether, amounts.wei)); - } - } - - mod valid_arithmetic { - use super::*; - - const TEST_VALUES: [(&str, &str, &str); 7] = [ - ("0", "0", "0"), - ("1", "2", "3"), - ("100000", "0", "100000"), - ("123456789", "987654321", "1111111110"), - ("1000000000000000", "2000000000000000", "3000000000000000"), - ( - "10000000000000000000001", - "20000000000000000000002", - "30000000000000000000003", - ), - ( - "1000000000000000000000000", - "1000000000000000000000000", - "2000000000000000000000000", - ), - ]; - - #[test] - fn test_valid_addition() { - TEST_VALUES - .iter() - .for_each(|(a, b, c)| test_addition(a, b, c)); - } - - #[test] - fn test_valid_subtraction() { - TEST_VALUES - .iter() - .for_each(|(a, b, c)| test_subtraction(c, b, a)); - } - } - - mod test_invalid { - use super::*; - - mod test_invalid_conversion { - use super::*; - - const INVALID_TEST_AMOUNTS: [AmountDenominationTestCase; 4] = [ - AmountDenominationTestCase { - wei: "1", - kwei: "1", - mwei: "1", - gwei: "1", - szabo: "1", - finney: "1", - ether: "1", - }, - AmountDenominationTestCase { - wei: "1", - kwei: "1000", - mwei: "1000000", - gwei: "1000000000", - szabo: "1000000000000", - finney: "1000000000000000", - ether: "1000000000000000000", - }, - AmountDenominationTestCase { - wei: "1234567891234567891", - kwei: "1234567891234567", - mwei: "1234567891234", - gwei: "1234567891", - szabo: "1234567", - finney: "1234", - ether: "1", - }, - AmountDenominationTestCase { - wei: "1000000000000000000000000", - kwei: "1000000000000000000000", - mwei: "1000000000000000000", - gwei: "1000000000000000", - szabo: "1000000000000", - finney: "1000000000", - ether: "1000001", - }, - ]; - - #[should_panic] - #[test] - fn test_invalid_finney_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_finney(amounts.finney, amounts.wei)); - } - - #[should_panic] - #[test] - fn test_invalid_szabo_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_szabo(amounts.szabo, amounts.wei)); - } - - #[should_panic] - #[test] - fn test_invalid_gwei_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_gwei(amounts.gwei, amounts.wei)); - } - - #[should_panic] - #[test] - fn test_invalid_mwei_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_mwei(amounts.mwei, amounts.wei)); - } - - #[should_panic] - #[test] - fn test_invalid_kwei_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_kwei(amounts.kwei, amounts.wei)); - } - - #[should_panic] - #[test] - fn test_invalid_eth_conversion() { - INVALID_TEST_AMOUNTS - .iter() - .for_each(|amounts| test_from_eth(amounts.ether, amounts.wei)); - } - } - } -} diff --git a/anychain-ethereum/src/lib.rs b/anychain-ethereum/src/lib.rs index 599ed5e..6b41dc6 100644 --- a/anychain-ethereum/src/lib.rs +++ b/anychain-ethereum/src/lib.rs @@ -1,32 +1,14 @@ -//! # Ethereum -//! -//! A library for generating Ethereum wallets. -//! #![cfg_attr(not(feature = "std"), no_std)] -#![warn(unused_extern_crates, dead_code)] -#![forbid(unsafe_code)] pub mod address; -pub use self::address::*; - -pub mod amount; -pub use self::amount::*; - pub mod format; -pub use self::format::*; - pub mod network; -pub use self::network::*; - pub mod public_key; -pub use self::public_key::*; - pub mod transaction; -pub use self::transaction::*; +mod util; -#[cfg(test)] -mod test_mod { - - #[test] - fn abi_test() {} -} +pub use self::address::*; +pub use self::format::*; +pub use self::network::*; +pub use self::public_key::*; +pub use self::transaction::*; diff --git a/anychain-ethereum/src/transaction.rs b/anychain-ethereum/src/transaction.rs index 1f4370f..8a920a0 100644 --- a/anychain-ethereum/src/transaction.rs +++ b/anychain-ethereum/src/transaction.rs @@ -1,386 +1,420 @@ -use crate::address::EthereumAddress; -use crate::amount::EthereumAmount; -use crate::format::EthereumFormat; -use crate::network::EthereumNetwork; -use crate::public_key::EthereumPublicKey; -use anychain_core::utilities::crypto::keccak256; -use anychain_core::{hex, PublicKey, Transaction, TransactionError, TransactionId}; +use crate::util::{adapt2, pad_zeros, restore_sender, trim_leading_zeros}; +use crate::{EthereumAddress, EthereumFormat, EthereumNetwork, EthereumPublicKey}; +use anychain_core::{ + hex, utilities::crypto::keccak256, Transaction, TransactionError, TransactionId, +}; use core::{fmt, marker::PhantomData, str::FromStr}; -use ethabi::ethereum_types::H160; -use ethabi::{Function, Param, ParamType, StateMutability, Token}; +use ethabi::{ethereum_types::H160, Function, Param, ParamType, StateMutability, Token}; use ethereum_types::U256; -use rlp::{decode_list, RlpStream}; +use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; use serde_json::{json, Value}; -use std::convert::TryInto; - -/// Trim the leading zeros of a byte stream and return it -fn trim_leading_zeros(v: &Vec) -> &[u8] { - let mut cnt: usize = 0; - for byte in v { - if *byte != 0 { - break; - } else { - cnt += 1; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EthereumTransactionParameters { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub to: EthereumAddress, + pub amount: U256, + pub data: Vec, +} + +impl EthereumTransactionParameters { + pub fn to_rlp(&self) -> Result { + let to = self + .to + .to_bytes() + .map_err(|e| TransactionError::Message(format!("{}", e)))?; + + let mut rlp = RlpStream::new(); + rlp.begin_list(9); + + rlp.append(&self.nonce); + rlp.append(&self.gas_price); + rlp.append(&self.gas_limit); + rlp.append(&to); + rlp.append(&self.amount); + rlp.append(&self.data); + + Ok(rlp) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EthereumTransactionSignature { + pub v: u32, + pub r: Vec, + pub s: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EthereumTransaction { + /// The address of the sender + pub sender: Option, + /// The transaction parameters (gas, gas_price, nonce, data) + pub params: EthereumTransactionParameters, + /// The transaction signature + pub signature: Option, + _network: PhantomData, +} + +impl EthereumTransaction { + pub fn restore_sender(&mut self) -> Result<(), TransactionError> { + if self.signature.is_none() { + return Err(TransactionError::Message( + "Signature is missing".to_string(), + )); } + + let sig = self.signature.clone().unwrap(); + self.signature = None; + + let r = sig.r.clone(); + let s = sig.s.clone(); + + let recid = (sig.v - 2 * N::CHAIN_ID - 35) as u8; + + let _sig = [r, s].concat(); + let msg = self.to_transaction_id()?.txid; + + let sender = restore_sender(msg, _sig, recid)?; + + self.sender = Some(sender); + self.signature = Some(sig); + + Ok(()) } - &v[cnt..] } -/// Prepend a number of zeros to 'v' to make it 'to_len' bytes long -fn pad_zeros(v: &mut Vec, to_len: usize) { - if v.len() < to_len { - let mut temp = v.clone(); - let len = v.len(); - v.clear(); - v.resize(to_len - len, 0); - v.append(&mut temp); +impl Transaction for EthereumTransaction { + type Address = EthereumAddress; + type Format = EthereumFormat; + type PublicKey = EthereumPublicKey; + type TransactionId = EthereumTransactionId; + type TransactionParameters = EthereumTransactionParameters; + + fn new(params: &Self::TransactionParameters) -> Result { + Ok(Self { + sender: None, + params: params.clone(), + signature: None, + _network: PhantomData, + }) + } + + fn sign(&mut self, rs: Vec, recid: u8) -> Result, TransactionError> { + if rs.len() != 64 { + return Err(TransactionError::Message(format!( + "Invalid signature length: {}", + rs.len() + ))); + } + let v = 2 * N::CHAIN_ID + 35 + (recid as u32); + let r = rs[..32].to_vec(); + let s = rs[32..].to_vec(); + self.signature = Some(EthereumTransactionSignature { v, r, s }); + self.to_bytes() + } + + fn to_bytes(&self) -> Result, TransactionError> { + match &self.signature { + Some(sig) => { + let mut rlp = self.params.to_rlp()?; + let r = trim_leading_zeros(&sig.r); + let s = trim_leading_zeros(&sig.s); + rlp.append(&sig.v); + rlp.append(&r); + rlp.append(&s); + Ok(rlp.out().to_vec()) + } + None => { + let mut rlp = self.params.to_rlp()?; + rlp.append(&N::CHAIN_ID); + rlp.append(&0u8); + rlp.append(&0u8); + Ok(rlp.out().to_vec()) + } + } + } + + fn from_bytes(tx: &[u8]) -> Result { + let rlp = Rlp::new(tx); + + let to = adapt2(rlp.val_at::>(3))?; + let to = hex::encode(to); + + let nonce = adapt2(rlp.val_at::(0))?; + let gas_price = adapt2(rlp.val_at::(1))?; + let gas_limit = adapt2(rlp.val_at::(2))?; + let to = EthereumAddress::from_str(&to)?; + let amount = adapt2(rlp.val_at::(4))?; + let data = adapt2(rlp.val_at::>(5))?; + + let v = adapt2(rlp.val_at::(6))?; + let mut r = adapt2(rlp.val_at::>(7))?; + let mut s = adapt2(rlp.val_at::>(8))?; + + pad_zeros(&mut r, 32); + pad_zeros(&mut s, 32); + + let params = EthereumTransactionParameters { + nonce, + gas_price, + gas_limit, + to, + amount, + data, + }; + + let mut tx = EthereumTransaction::::new(¶ms)?; + + if !r.is_empty() && !s.is_empty() { + let sig = EthereumTransactionSignature { v, r, s }; + tx.signature = Some(sig); + tx.restore_sender()?; + } + + Ok(tx) + } + + fn to_transaction_id(&self) -> Result { + Ok(Self::TransactionId { + txid: keccak256(&self.to_bytes()?).to_vec(), + }) } } -pub fn encode_transfer(func_name: &str, address: &EthereumAddress, amount: U256) -> Vec { - #[allow(deprecated)] - let func = Function { - name: func_name.to_string(), - inputs: vec![ - Param { - name: "address".to_string(), - kind: ParamType::Address, - internal_type: None, - }, - Param { - name: "amount".to_string(), - kind: ParamType::Uint(256), - internal_type: None, - }, - ], - outputs: vec![], - constant: None, - state_mutability: StateMutability::Payable, - }; +impl FromStr for EthereumTransaction { + type Err = TransactionError; - let tokens = vec![ - Token::Address(H160::from_slice(&address.to_bytes().unwrap())), - Token::Uint(amount), - ]; + fn from_str(tx: &str) -> Result { + let tx = match &tx[..2] { + "0x" => &tx[2..], + _ => tx, + }; + Self::from_bytes(&hex::decode(tx)?) + } +} - func.encode_input(&tokens).unwrap() +impl fmt::Display for EthereumTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "0x{}", + &hex::encode(match self.to_bytes() { + Ok(transaction) => transaction, + _ => return Err(fmt::Error), + }) + ) + } } -/// Represents the parameters for an Ethereum transaction #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EthereumTransactionParameters { - /// The address of the receiver - pub receiver: EthereumAddress, - /// The amount (in wei) - pub amount: EthereumAmount, - /// The transaction gas limit - pub gas: U256, - /// The transaction gas price in wei - pub gas_price: EthereumAmount, - /// The nonce of the Ethereum account +pub struct Eip1559TransactionParameters { + pub chain_id: u32, pub nonce: U256, - /// The transaction data + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub to: EthereumAddress, + pub amount: U256, pub data: Vec, + pub access_list: Vec, } -impl EthereumTransactionParameters { - pub fn decode_data(&self) -> Result { - if self.data.len() < 4 { - return Err(TransactionError::Message("Illegal data".to_string())); - } +impl Eip1559TransactionParameters { + pub fn to_rlp(&self, array_len: usize) -> Result { + let to = self + .to + .to_bytes() + .map_err(|e| TransactionError::Message(format!("{}", e)))?; - let selector = &self.data[..4]; - - match selector { - // function selector for 'transfer(address,uint256)' - [169, 5, 156, 187] => { - #[allow(deprecated)] - let func = Function { - name: "transfer".to_string(), - inputs: vec![ - Param { - name: "to".to_string(), - kind: ParamType::Address, - internal_type: None, - }, - Param { - name: "amount".to_string(), - kind: ParamType::Uint(256), - internal_type: None, - }, - ], - outputs: vec![], - constant: None, - state_mutability: StateMutability::Payable, - }; - match func.decode_input(&self.data[4..]) { - Ok(tokens) => { - let to = hex::encode(tokens[0].clone().into_address().unwrap().as_bytes()); - let amount = tokens[1].clone().into_uint().unwrap().as_u128(); - Ok(json!({ - "function": "transfer", - "params": { - "to": to, - "amount": amount.to_string(), - } - })) - } - Err(e) => Err(TransactionError::Message(e.to_string())), - } - } - _ => Err(TransactionError::Message( - "Unsupported contract function".to_string(), - )), - } + let mut rlp = RlpStream::new(); + rlp.begin_list(array_len); + + rlp.append(&self.chain_id); + rlp.append(&self.nonce); + rlp.append(&self.max_priority_fee_per_gas); + rlp.append(&self.max_fee_per_gas); + rlp.append(&self.gas_limit); + rlp.append(&to); + rlp.append(&self.amount); + rlp.append(&self.data); + rlp.append_list(&self.access_list); + + Ok(rlp) } } -/// Represents an Ethereum transaction signature #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct EthereumTransactionSignature { - /// The V field of the signature protected with a chain_id - pub v: Vec, - /// The R field of the signature +pub struct Eip1559TransactionSignature { + pub y_parity: bool, pub r: Vec, - /// The S field of the signature pub s: Vec, } -/// Represents an Ethereum transaction id #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct EthereumTransactionId { - pub txid: Vec, +pub struct AccessItem { + pub address: EthereumAddress, + pub storage_keys: Vec>, } -impl TransactionId for EthereumTransactionId {} +impl Encodable for AccessItem { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + s.append(&self.address.to_bytes().unwrap()); + s.append_list::, Vec>(&self.storage_keys); + } +} -impl fmt::Display for EthereumTransactionId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "0x{}", hex::encode(&self.txid)) +impl Decodable for AccessItem { + fn decode(rlp: &rlp::Rlp) -> Result { + let address = hex::encode(rlp.val_at::>(0)?); + let address = EthereumAddress::from_str(&address).unwrap(); + let storage_keys = rlp.list_at::>(1)?; + Ok(Self { + address, + storage_keys, + }) } } -/// Represents an Ethereum transaction #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EthereumTransaction { - /// The address of the sender +pub struct Eip1559Transaction { pub sender: Option, - /// The transaction parameters (gas, gas_price, nonce, data) - pub parameters: EthereumTransactionParameters, - /// The transaction signature - pub signature: Option, + pub params: Eip1559TransactionParameters, + pub signature: Option, _network: PhantomData, } -impl Transaction for EthereumTransaction { +impl Eip1559Transaction { + pub fn restore_sender(&mut self) -> Result<(), TransactionError> { + if self.signature.is_none() { + return Err(TransactionError::Message( + "Signature is missing".to_string(), + )); + } + + let sig = self.signature.clone().unwrap(); + self.signature = None; + + let recid = match sig.y_parity { + true => 1, + false => 0, + } as u8; + + let _sig = [sig.r.clone(), sig.s.clone()].concat(); + let msg = self.to_transaction_id()?.txid; + + let sender = restore_sender(msg, _sig, recid)?; + + self.sender = Some(sender); + self.signature = Some(sig); + + Ok(()) + } +} + +impl Transaction for Eip1559Transaction { type Address = EthereumAddress; type Format = EthereumFormat; type PublicKey = EthereumPublicKey; type TransactionId = EthereumTransactionId; - type TransactionParameters = EthereumTransactionParameters; + type TransactionParameters = Eip1559TransactionParameters; - /// Returns an unsigned transaction given the transaction parameters. - fn new(parameters: &Self::TransactionParameters) -> Result { + fn new(params: &Self::TransactionParameters) -> Result { Ok(Self { sender: None, - parameters: parameters.clone(), + params: params.clone(), signature: None, _network: PhantomData, }) } - /// Returns a signed transaction given the {r,s,recid}. fn sign(&mut self, rs: Vec, recid: u8) -> Result, TransactionError> { - let message = libsecp256k1::Message::parse_slice(&self.to_transaction_id()?.txid) - .map_err(|error| TransactionError::Crate("libsecp256k1", format!("{:?}", error)))?; - let recovery_id = libsecp256k1::RecoveryId::parse(recid) - .map_err(|error| TransactionError::Crate("libsecp256k1", format!("{:?}", error)))?; - - let public_key = EthereumPublicKey::from_secp256k1_public_key( - libsecp256k1::recover( - &message, - &libsecp256k1::Signature::parse_standard_slice(rs.as_slice()).map_err(|error| { - TransactionError::Crate("libsecp256k1", format!("{:?}", error)) - })?, - &recovery_id, - ) - .map_err(|error| TransactionError::Crate("libsecp256k1", format!("{:?}", error)))?, - ); - self.sender = Some(public_key.to_address(&EthereumFormat::Standard)?); - self.signature = Some(EthereumTransactionSignature { - v: (u32::from(recid) + N::CHAIN_ID * 2 + 35) - .to_be_bytes() - .to_vec(), // EIP155 - r: rs[..32].to_vec(), - s: rs[32..64].to_vec(), - }); + if rs.len() != 64 { + return Err(TransactionError::Message(format!( + "Invalid signature length: {}", + rs.len() + ))); + } + let y_parity = recid == 1; + let r = rs[..32].to_vec(); + let s = rs[32..].to_vec(); + self.signature = Some(Eip1559TransactionSignature { y_parity, r, s }); self.to_bytes() } - /// Returns a transaction given the transaction bytes. - /// - fn from_bytes(transaction: &[u8]) -> Result { - let list: Vec> = decode_list(transaction); - if list.len() != 9 { - return Err(TransactionError::InvalidRlpLength(list.len())); - } - - let parameters = EthereumTransactionParameters { - receiver: EthereumAddress::from_str(&hex::encode(&list[3]))?, - amount: match list[4].is_empty() { - true => EthereumAmount::from_u256(U256::zero()), - false => EthereumAmount::from_u256(U256::from(list[4].as_slice())), - }, - gas: match list[2].is_empty() { - true => U256::zero(), - false => U256::from(list[2].as_slice()), - }, - gas_price: match list[1].is_empty() { - true => EthereumAmount::from_u256(U256::zero()), - false => EthereumAmount::from_u256(U256::from(list[1].as_slice())), - }, - nonce: match list[0].is_empty() { - true => U256::zero(), - false => U256::from(list[0].as_slice()), - }, - data: list[5].clone(), - }; - - match list[7].is_empty() && list[8].is_empty() { - true => { - // Raw transaction - Ok(Self { - sender: None, - parameters, - signature: None, - _network: PhantomData, - }) - } - false => { - // Signed transaction - let mut v = list[6].clone(); - pad_zeros(&mut v, 4); - let v: [u8; 4] = v.try_into().unwrap(); - let v = u32::from_be_bytes(v); - let recovery_id = libsecp256k1::RecoveryId::parse((v - N::CHAIN_ID * 2 - 35) as u8) - .map_err(|error| { - TransactionError::Crate("libsecp256k1", format!("{:?}", error)) - })?; - let mut r = list[7].clone(); - pad_zeros(&mut r, 32); - let mut s = list[8].clone(); - pad_zeros(&mut s, 32); - let signature = [r.clone(), s.clone()].concat(); - let raw_transaction = Self { - sender: None, - parameters: parameters.clone(), - signature: None, - _network: PhantomData, - }; - let message = - libsecp256k1::Message::parse_slice(&raw_transaction.to_transaction_id()?.txid) - .map_err(|error| { - TransactionError::Crate("libsecp256k1", format!("{:?}", error)) - })?; - let public_key = EthereumPublicKey::from_secp256k1_public_key( - libsecp256k1::recover( - &message, - &libsecp256k1::Signature::parse_standard_slice(signature.as_slice()) - .map_err(|error| { - TransactionError::Crate("libsecp256k1", format!("{:?}", error)) - })?, - &recovery_id, - ) - .map_err(|error| { - TransactionError::Crate("libsecp256k1", format!("{:?}", error)) - })?, - ); - - Ok(Self { - sender: Some(public_key.to_address(&EthereumFormat::Standard)?), - parameters, - signature: Some(EthereumTransactionSignature { - v: v.to_be_bytes().to_vec(), - r, - s, - }), - _network: PhantomData, - }) + fn to_bytes(&self) -> Result, TransactionError> { + let rlp = match &self.signature { + Some(sig) => { + let mut rlp = self.params.to_rlp(12)?; + let r = trim_leading_zeros(&sig.r); + let s = trim_leading_zeros(&sig.s); + rlp.append(&sig.y_parity); + rlp.append(&r); + rlp.append(&s); + rlp.out().to_vec() } - } + None => self.params.to_rlp(9)?.out().to_vec(), + }; + Ok([vec![2u8], rlp].concat()) } - /// Returns the transaction in bytes. - /// - fn to_bytes(&self) -> Result, TransactionError> { - // Returns an encoded transaction in Recursive Length Prefix (RLP) format. - // https://github.com/ethereum/wiki/wiki/RLP - fn encode_transaction( - transaction_rlp: &mut RlpStream, - parameters: &EthereumTransactionParameters, - ) -> Result<(), TransactionError> { - transaction_rlp.append(¶meters.nonce); - transaction_rlp.append(¶meters.gas_price.0); - transaction_rlp.append(¶meters.gas); - transaction_rlp.append(&hex::decode(¶meters.receiver.to_string()[2..])?); - transaction_rlp.append(¶meters.amount.0); - transaction_rlp.append(¶meters.data); - Ok(()) - } + fn from_bytes(tx: &[u8]) -> Result { + let rlp = Rlp::new(&tx[1..]); - // Returns the raw transaction (in RLP). - fn raw_transaction( - parameters: &EthereumTransactionParameters, - ) -> Result { - let mut transaction_rlp = RlpStream::new(); - transaction_rlp.begin_list(9); - encode_transaction(&mut transaction_rlp, parameters)?; - let chain_id = N::CHAIN_ID.to_be_bytes().to_vec(); - let chain_id = trim_leading_zeros(&chain_id); - transaction_rlp.append(&chain_id); - transaction_rlp.append(&0u8); - transaction_rlp.append(&0u8); - Ok(transaction_rlp) - } + let to = adapt2(rlp.val_at::>(5))?; + let to = hex::encode(to); - // Returns the signed transaction (in RLP). - fn signed_transaction( - parameters: &EthereumTransactionParameters, - signature: &EthereumTransactionSignature, - ) -> Result { - let mut transaction_rlp = RlpStream::new(); - transaction_rlp.begin_list(9); - encode_transaction(&mut transaction_rlp, parameters)?; - // trim the leading zeros of v - let v = trim_leading_zeros(&signature.v); - transaction_rlp.append(&v); - // trim the leading zeros of r - let r = trim_leading_zeros(&signature.r); - transaction_rlp.append(&r); - // trim the leading zeros of s - let s = trim_leading_zeros(&signature.s); - transaction_rlp.append(&s); - Ok(transaction_rlp) - } + let chain_id = adapt2(rlp.val_at::(0))?; + let nonce = adapt2(rlp.val_at::(1))?; + let max_priority_fee_per_gas = adapt2(rlp.val_at::(2))?; + let max_fee_per_gas = adapt2(rlp.val_at::(3))?; + let gas_limit = adapt2(rlp.val_at::(4))?; + let to = EthereumAddress::from_str(&to)?; + let amount = adapt2(rlp.val_at::(6))?; + let data = adapt2(rlp.val_at::>(7))?; + let access_list = adapt2(rlp.list_at::(8))?; - match &self.signature { - Some(signature) => Ok(signed_transaction(&self.parameters, signature)? - .out() - .to_vec()), - None => Ok(raw_transaction::(&self.parameters)?.out().to_vec()), + let y_parity = adapt2(rlp.val_at::(9))?; + let mut r = adapt2(rlp.val_at::>(10))?; + let mut s = adapt2(rlp.val_at::>(11))?; + + pad_zeros(&mut r, 32); + pad_zeros(&mut s, 32); + + let params = Eip1559TransactionParameters { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + amount, + data, + access_list, + }; + + let mut tx = Eip1559Transaction::::new(¶ms)?; + + if !r.is_empty() && !s.is_empty() { + let sig = Eip1559TransactionSignature { y_parity, r, s }; + tx.signature = Some(sig); + tx.restore_sender()?; } + + Ok(tx) } - /// Returns the hash of the signed transaction, if the signature is present. - /// Otherwise, returns the hash of the raw transaction. fn to_transaction_id(&self) -> Result { Ok(Self::TransactionId { - txid: Vec::::from(&keccak256(&self.to_bytes()?)[..]), + txid: keccak256(&self.to_bytes()?).to_vec(), }) } } -impl FromStr for EthereumTransaction { +impl FromStr for Eip1559Transaction { type Err = TransactionError; fn from_str(tx: &str) -> Result { @@ -392,7 +426,7 @@ impl FromStr for EthereumTransaction { } } -impl fmt::Display for EthereumTransaction { +impl fmt::Display for Eip1559Transaction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -404,3 +438,165 @@ impl fmt::Display for EthereumTransaction { ) } } + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EthereumTransactionId { + pub txid: Vec, +} + +impl TransactionId for EthereumTransactionId {} + +impl fmt::Display for EthereumTransactionId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x{}", hex::encode(&self.txid)) + } +} + +pub fn encode_transfer(func_name: &str, address: &EthereumAddress, amount: U256) -> Vec { + #[allow(deprecated)] + let func = Function { + name: func_name.to_string(), + inputs: vec![ + Param { + name: "address".to_string(), + kind: ParamType::Address, + internal_type: None, + }, + Param { + name: "amount".to_string(), + kind: ParamType::Uint(256), + internal_type: None, + }, + ], + outputs: vec![], + constant: None, + state_mutability: StateMutability::Payable, + }; + + let tokens = vec![ + Token::Address(H160::from_slice(&address.to_bytes().unwrap())), + Token::Uint(amount), + ]; + + func.encode_input(&tokens).unwrap() +} + +pub fn decode_transfer(data: Vec) -> Result { + if data.len() < 4 { + return Err(TransactionError::Message("Illegal data".to_string())); + } + + let selector = &data[..4]; + + match selector { + // function selector for 'transfer(address,uint256)' + [169, 5, 156, 187] => { + #[allow(deprecated)] + let func = Function { + name: "transfer".to_string(), + inputs: vec![ + Param { + name: "to".to_string(), + kind: ParamType::Address, + internal_type: None, + }, + Param { + name: "amount".to_string(), + kind: ParamType::Uint(256), + internal_type: None, + }, + ], + outputs: vec![], + constant: None, + state_mutability: StateMutability::Payable, + }; + match func.decode_input(&data[4..]) { + Ok(tokens) => { + let to = hex::encode(tokens[0].clone().into_address().unwrap().as_bytes()); + let amount = tokens[1].clone().into_uint().unwrap().as_u128(); + Ok(json!({ + "function": "transfer", + "params": { + "to": to, + "amount": amount + } + })) + } + Err(e) => Err(TransactionError::Message(e.to_string())), + } + } + _ => Err(TransactionError::Message( + "Unsupported contract function".to_string(), + )), + } +} + +// mod tests { +// use super::*; +// use crate::Sepolia; + +// #[test] +// fn test_legacy_tx() { +// let params = EthereumTransactionParameters { +// nonce: U256::from_dec_str("6").unwrap(), +// gas_price: U256::from_dec_str("20000000000").unwrap(), +// gas_limit: U256::from_dec_str("21000").unwrap(), +// to: EthereumAddress::from_str("0xf7a63003b8ef116939804b4c2dd49290a39c4d97").unwrap(), +// amount: U256::from_dec_str("10000000000000000").unwrap(), +// data: vec![], +// }; +// let mut tx = EthereumTransaction::::new(¶ms).unwrap(); +// let msg = tx.to_transaction_id().unwrap().txid; +// let msg = libsecp256k1::Message::parse_slice(&msg).unwrap(); + +// let sk = "08d586ed207046d6476f92fd4852be3830a9d651fc148d6fa5a6f15b77ba5df0"; +// let sk = hex::decode(sk).unwrap(); +// let sk = libsecp256k1::SecretKey::parse_slice(&sk).unwrap(); + +// let (sig, recid) = libsecp256k1::sign(&msg, &sk); + +// let sig = sig.serialize().to_vec(); +// let recid = recid.serialize(); + +// let _ = tx.sign(sig, recid); + +// println!("{}", tx); +// } + +// #[test] +// fn test_eip1559_tx() { +// let params = Eip1559TransactionParameters { +// chain_id: Sepolia::CHAIN_ID, +// nonce: U256::from_dec_str("4").unwrap(), +// max_priority_fee_per_gas: U256::from_dec_str("100000000000").unwrap(), +// max_fee_per_gas: U256::from_dec_str("200000000000").unwrap(), +// gas_limit: U256::from_dec_str("21000").unwrap(), +// to: EthereumAddress::from_str("0xf7a63003b8ef116939804b4c2dd49290a39c4d97").unwrap(), +// amount: U256::from_dec_str("10000000000000000").unwrap(), +// data: vec![], +// access_list: vec![], +// }; +// let mut tx = Eip1559Transaction::::new(¶ms).unwrap(); +// let msg = tx.to_transaction_id().unwrap().txid; +// let msg = libsecp256k1::Message::parse_slice(&msg).unwrap(); + +// let sk = "08d586ed207046d6476f92fd4852be3830a9d651fc148d6fa5a6f15b77ba5df0"; +// let sk = hex::decode(sk).unwrap(); +// let sk = libsecp256k1::SecretKey::parse_slice(&sk).unwrap(); + +// let (sig, recid) = libsecp256k1::sign(&msg, &sk); +// let sig = sig.serialize().to_vec(); +// let recid = recid.serialize(); + +// let _ = tx.sign(sig, recid); + +// println!("{}", tx); +// } + +// #[test] +// fn test() { +// let tx = "0x02f87683aa36a70485174876e800852e90edd00082520894f7a63003b8ef116939804b4c2dd49290a39c4d97872386f26fc1000080c001a077233c9ef0d1a3211f844865172aa31ef716b98f4d82e7c86c2cd7050455e243a0678fdd0f3dd4e0bce65642e24368fa22bb34a8b4542c6bcac55e943c051dbb56"; +// let tx = Eip1559Transaction::::from_str(tx).unwrap(); +// println!("{}", tx); +// } +// } diff --git a/anychain-ethereum/src/util.rs b/anychain-ethereum/src/util.rs new file mode 100644 index 0000000..dd8101f --- /dev/null +++ b/anychain-ethereum/src/util.rs @@ -0,0 +1,57 @@ +use crate::{EthereumAddress, EthereumFormat, EthereumPublicKey}; +use anychain_core::{PublicKey, TransactionError}; +use libsecp256k1::{recover, Error, Message, RecoveryId, Signature}; + +/// Trim the leading zeros of a byte stream and return it +pub(crate) fn trim_leading_zeros(v: &Vec) -> &[u8] { + let mut cnt: usize = 0; + for byte in v { + if *byte != 0 { + break; + } else { + cnt += 1; + } + } + &v[cnt..] +} + +/// Prepend a number of zeros to 'v' to make it 'to_len' bytes long +pub(crate) fn pad_zeros(v: &mut Vec, to_len: usize) { + if v.len() < to_len { + let mut temp = v.clone(); + let len = v.len(); + v.clear(); + v.resize(to_len - len, 0); + v.append(&mut temp); + } +} + +pub(crate) fn adapt1(v: Result) -> Result { + match v { + Ok(t) => Ok(t), + Err(e) => Err(TransactionError::Message(format!( + "libsecp256k1 error: {}", + e + ))), + } +} + +pub(crate) fn adapt2(v: Result) -> Result { + match v { + Ok(t) => Ok(t), + Err(e) => Err(TransactionError::Message(format!("rlp error:{}", e))), + } +} + +pub(crate) fn restore_sender( + msg: Vec, + sig: Vec, + recid: u8, +) -> Result { + let recid = adapt1(RecoveryId::parse(recid))?; + let sig = adapt1(Signature::parse_standard_slice(&sig))?; + let msg = adapt1(Message::parse_slice(&msg))?; + let pk = adapt1(recover(&msg, &sig, &recid))?; + let pk = EthereumPublicKey::from_secp256k1_public_key(pk); + Ok(pk.to_address(&EthereumFormat::Standard)?) +}