From fb38cd020b0eb077dc56308537ca5d3929fb7873 Mon Sep 17 00:00:00 2001 From: ermvrs Date: Tue, 26 Nov 2024 20:01:52 +0300 Subject: [PATCH] rlp encoding for eip1559 --- .snfoundry_cache/.prev_tests_failed | 1 + Scarb.lock | 52 ++++++++++++ Scarb.toml | 3 + src/accounts/encoding.cairo | 124 ++++++++++++++++++++++++++++ src/accounts/utils.cairo | 2 +- src/lib.cairo | 1 + src/utils/bytes.cairo | 19 ++++- src/utils/helpers.cairo | 2 +- src/utils/transaction/eip2930.cairo | 75 ++++------------- 9 files changed, 217 insertions(+), 62 deletions(-) create mode 100644 src/accounts/encoding.cairo diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed index 5c7a929..000d4f2 100644 --- a/.snfoundry_cache/.prev_tests_failed +++ b/.snfoundry_cache/.prev_tests_failed @@ -1 +1,2 @@ +rosettacontracts::accounts::encoding::tests::encode_transaction rosettacontracts_integrationtest::factory_tests::deploy_check_initials diff --git a/Scarb.lock b/Scarb.lock index 2c0acad..827c29b 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,6 +1,55 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_bytes", + "alexandria_math", + "alexandria_numeric", +] + +[[package]] +name = "alexandria_math" +version = "0.2.1" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" + +[[package]] +name = "alexandria_numeric" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_data_structures", +] + [[package]] name = "openzeppelin" version = "0.17.0" @@ -119,6 +168,9 @@ checksum = "sha256:36d93e353f42fd6b824abcd8b4b51c3f5d02c893c5f886ae81403b0368aa5 name = "rosettacontracts" version = "0.1.0" dependencies = [ + "alexandria_data_structures", + "alexandria_encoding", + "alexandria_numeric", "openzeppelin", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index 519ad4f..727a42d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -11,6 +11,9 @@ sierra = true [dependencies] starknet = "2.8.2" openzeppelin = "0.17.0" +alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_data_structures = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_numeric = { git = "https://github.com/keep-starknet-strange/alexandria.git" } [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } diff --git a/src/accounts/encoding.cairo b/src/accounts/encoding.cairo new file mode 100644 index 0000000..800eeeb --- /dev/null +++ b/src/accounts/encoding.cairo @@ -0,0 +1,124 @@ +use starknet::{EthAddress}; +use core::byte_array::{ByteArrayTrait}; +use crate::utils::transaction::eip2930::{AccessListItem, AccessListItemTrait}; +use crate::utils::bytes::{ByteArrayExTrait}; +use alexandria_encoding::rlp::{RLPItem, RLPTrait}; + +#[derive(Copy, Drop, Clone, PartialEq, Serde)] +pub struct Eip1559Transaction { + chain_id: u64, + nonce: u64, + max_priority_fee_per_gas: u128, + max_fee_per_gas: u128, + gas_limit: u64, + to: EthAddress, + value: u256, + input: Span, // u256 or felt? + access_list: Span, +} + +pub fn rlp_encode_eip1559(tx: Eip1559Transaction) -> Span { + let chain_id = RLPItem::String(deserialize_bytes_non_zeroes(tx.chain_id.into(),8)); + let nonce = RLPItem::String(deserialize_bytes_non_zeroes(tx.nonce.into(),8)); + let max_priority_fee_per_gas = RLPItem::String(deserialize_bytes_non_zeroes(tx.max_priority_fee_per_gas.into(), 16)); + let max_fee_per_gas = RLPItem::String(deserialize_bytes_non_zeroes(tx.max_fee_per_gas.into(), 16)); + let gas_limit = RLPItem::String(deserialize_bytes_non_zeroes(tx.gas_limit.into(), 8)); + let to = RLPItem::String(deserialize_bytes(tx.to.into(), 20)); + let value = RLPItem::String(deserialize_bytes_non_zeroes(tx.value.try_into().unwrap(), 32)); + let input = RLPItem::String(tx.input); + + let mut access_arr = array![]; + let mut access_list_items = tx.access_list; + loop { + match access_list_items.pop_front() { + Option::None => { break; }, + Option::Some(item) => { + access_arr.append(item.to_rlp_items()); + } + }; + }; + + let access_list = RLPItem::List(access_arr.span()); + + + let mut rlp_inputs = RLPItem::List(array![chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, input, access_list].span()); + let mut encoded_tx = array![2_u8]; + + encoded_tx.append_span(RLPTrait::encode(array![rlp_inputs].span()).unwrap()); + encoded_tx.span() +} + +pub fn deserialize_bytes(value: felt252, len: usize) -> Span { + let mut ba = Default::default(); + ba.append_word(value, len); + ba.into_bytes() +} + +pub fn deserialize_bytes_non_zeroes(value: felt252, len: usize) -> Span { + let mut ba = Default::default(); + ba.append_word(value, len); + ba.into_bytes_without_initial_zeroes() +} + + +#[cfg(test)] +mod tests { + use crate::accounts::encoding::{Eip1559Transaction, rlp_encode_eip1559, deserialize_bytes_non_zeroes}; + #[test] + fn encode_transaction() { + let tx = Eip1559Transaction { + chain_id: 2933, + nonce: 1, + max_priority_fee_per_gas: 1000000000, + max_fee_per_gas: 1000000000, + gas_limit: 21000, + to: 0x11655f4Ee2A5B66F9DCbe758e9FcdCd3eBF95eE5.try_into().unwrap(), + value: 0x0, + input: array![0xAB, 0xCA, 0xBC].span(), + access_list: array![].span() + }; + + let encoded = rlp_encode_eip1559(tx); + assert_eq!(encoded.len(), 92); + assert_eq!(*encoded.at(0), 0x02); + assert_eq!(*encoded.at(1), 0xEC); + assert_eq!(*encoded.at(2), 0x82); + assert_eq!(*encoded.at(3), 0x0B); + assert_eq!(*encoded.at(4), 0x75); + } + + #[test] + fn test_tx_bytes_decoding() { + let value = 0x567312; + + let decoded_value: Span = deserialize_bytes_non_zeroes(value, 8); + + assert_eq!(*decoded_value.at(0), 0x56); + assert_eq!(*decoded_value.at(1), 0x73); + assert_eq!(*decoded_value.at(2), 0x12); + } + + #[test] + fn test_tx_bytes_decoding_initial_zeroes() { + let value = 0x00567312; + + let decoded_value: Span = deserialize_bytes_non_zeroes(value, 8); + + assert_eq!(*decoded_value.at(0), 0x56); + assert_eq!(*decoded_value.at(1), 0x73); + assert_eq!(*decoded_value.at(2), 0x12); + } + + #[test] + fn test_tx_bytes_decoding_zeroes() { + let value = 0x005673120055; + + let decoded_value: Span = deserialize_bytes_non_zeroes(value, 8); + + assert_eq!(*decoded_value.at(0), 0x56); + assert_eq!(*decoded_value.at(1), 0x73); + assert_eq!(*decoded_value.at(2), 0x12); + assert_eq!(*decoded_value.at(3), 0x00); + assert_eq!(*decoded_value.at(4), 0x55); + } +} \ No newline at end of file diff --git a/src/accounts/utils.cairo b/src/accounts/utils.cairo index a1611a4..a42fbf7 100644 --- a/src/accounts/utils.cairo +++ b/src/accounts/utils.cairo @@ -8,7 +8,6 @@ use crate::utils::traits::SpanDefault; use crate::errors::{EthTransactionError, RLPError, RLPErrorTrait}; use crate::utils::bytes::{U8SpanExTrait}; use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; -use core::num::traits::SaturatingSub; #[derive(Copy, Drop, Serde)] pub struct EthSignature { @@ -131,6 +130,7 @@ pub fn decode_encoded_eip1559_transaction(ref encoded_tx: Span) -> Result bool { true } diff --git a/src/lib.cairo b/src/lib.cairo index 7dcd0be..ba24e22 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -4,6 +4,7 @@ pub mod lens; pub mod accounts { pub mod base; pub mod utils; + pub mod encoding; } pub mod utils; diff --git a/src/utils/bytes.cairo b/src/utils/bytes.cairo index a61c600..67c224e 100644 --- a/src/utils/bytes.cairo +++ b/src/utils/bytes.cairo @@ -2,7 +2,7 @@ use core::cmp::min; use core::keccak::{cairo_keccak}; use core::num::traits::{Zero, One, Bounded, BitSize, SaturatingAdd}; use core::traits::{BitAnd}; -use crate::utils::constants::{POW_2, POW_256_1, POW_256_REV}; +use crate::utils::constants::{POW_256_1}; use crate::utils::math::{Bitshift}; use crate::utils::integer::{BytesUsedTrait, ByteSize, U256Trait}; @@ -526,4 +526,21 @@ pub impl ByteArrayExt of ByteArrayExTrait { }; output.span() } + + fn into_bytes_without_initial_zeroes(self: ByteArray) -> Span { + let mut output: Array = Default::default(); + let mut firstValue = false; + for i in 0..self.len() { + let val = self[i]; + if(val == 0 && !firstValue) { + continue; + } + output.append(val); + if(!firstValue) { + firstValue = true; + continue; + }; + }; + output.span() + } } diff --git a/src/utils/helpers.cairo b/src/utils/helpers.cairo index 0fe4b43..a36830a 100644 --- a/src/utils/helpers.cairo +++ b/src/utils/helpers.cairo @@ -9,7 +9,7 @@ use core::pedersen::PedersenTrait; use core::starknet::{EthAddress, ContractAddress, ClassHash}; use core::traits::TryInto; use crate::utils::constants::{CONTRACT_ADDRESS_PREFIX, MAX_ADDRESS}; -use crate::utils::constants::{POW_2, POW_256_1, POW_256_REV}; +use crate::utils::constants::{POW_2, POW_256_REV}; use crate::utils::array::{ArrayExtTrait}; use crate::utils::{U256TryIntoContractAddress, EthAddressIntoU256, BoolIntoNumeric}; diff --git a/src/utils/transaction/eip2930.cairo b/src/utils/transaction/eip2930.cairo index cd1698d..c78c13d 100644 --- a/src/utils/transaction/eip2930.cairo +++ b/src/utils/transaction/eip2930.cairo @@ -1,8 +1,9 @@ use core::starknet::EthAddress; use crate::errors::{EthTransactionError, RLPError, RLPErrorTrait}; use crate::utils::transaction::common::TxKind; -use crate::utils::rlp::{RLPItem, RLPHelpersTrait}; +use alexandria_encoding::rlp::{RLPItem, RLPTrait}; use crate::utils::traits::SpanDefault; +use crate::accounts::encoding::{deserialize_bytes, deserialize_bytes_non_zeroes}; #[derive(Copy, Drop, Serde, PartialEq, Debug)] @@ -23,6 +24,20 @@ pub impl AccessListItemImpl of AccessListItemTrait { storage_keys_arr.span() } + + fn to_rlp_items(self: @AccessListItem) -> RLPItem { + let AccessListItem { ethereum_address, mut storage_keys } = *self; + + let mut storage_keys_arr = array![]; + for storage_key in storage_keys { + storage_keys_arr.append(RLPItem::String(deserialize_bytes((*storage_key).try_into().unwrap(), 32))); + }; + + let addr = RLPItem::String(deserialize_bytes(ethereum_address.into(), 20)); + let keys = RLPItem::List(storage_keys_arr.span()); + + RLPItem::List(array![addr, keys].span()) + } } @@ -68,61 +83,3 @@ pub struct TxEip2930 { /// input data of the message call; pub input: Span, } - - -#[generate_trait] -pub impl _impl of TxEip2930Trait { - /// Decodes the RLP-encoded fields into a TxEip2930 struct. - /// - /// # Arguments - /// - /// * `data` - A span of RLPItems containing the encoded transaction fields - /// - /// # Returns - /// - /// A Result containing either the decoded TxEip2930 struct or an EthTransactionError - fn decode_fields(ref data: Span) -> Result { - let boxed_fields = data - .multi_pop_front::<8>() - .ok_or(EthTransactionError::RLPError(RLPError::InputTooShort))?; - let [ - chain_id_encoded, - nonce_encoded, - gas_price_encoded, - gas_limit_encoded, - to_encoded, - value_encoded, - input_encoded, - access_list_encoded - ] = - (*boxed_fields) - .unbox(); - - let chain_id = chain_id_encoded.parse_u64_from_string().map_err()?; - let nonce = nonce_encoded.parse_u64_from_string().map_err()?; - let gas_price = gas_price_encoded.parse_u128_from_string().map_err()?; - let gas_limit = gas_limit_encoded.parse_u64_from_string().map_err()?; - let to = to_encoded.try_parse_address_from_string().map_err()?; - let value = value_encoded.parse_u256_from_string().map_err()?; - let input = input_encoded.parse_bytes_from_string().map_err()?; - let access_list = access_list_encoded.parse_access_list().map_err()?; - - let txkind_to = match to { - Option::Some(to) => { TxKind::Call(to) }, - Option::None => { TxKind::Create } - }; - - Result::Ok( - TxEip2930 { - chain_id: chain_id, - nonce, - gas_price, - gas_limit, - input, - access_list, - to: txkind_to, - value, - } - ) - } -} \ No newline at end of file