From cf3d11aebbdf9e51335df14c63da900ec4b4062b Mon Sep 17 00:00:00 2001 From: Alex Miao Date: Wed, 20 Sep 2023 11:02:35 -0700 Subject: [PATCH] fix(uo): fix user operation encoding and size calculations --- crates/rundler/src/common/gas.rs | 7 +-- crates/rundler/src/op_pool/mempool/mod.rs | 7 ++- crates/rundler/src/op_pool/mempool/pool.rs | 12 ++--- crates/rundler/src/rpc/eth/estimation.rs | 20 +++---- crates/types/src/user_operation.rs | 63 ++++++++++++++++++++-- 5 files changed, 84 insertions(+), 25 deletions(-) diff --git a/crates/rundler/src/common/gas.rs b/crates/rundler/src/common/gas.rs index f97f40654..4cf7c20fc 100644 --- a/crates/rundler/src/common/gas.rs +++ b/crates/rundler/src/common/gas.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use anyhow::Context; use ethers::{ + abi::AbiEncode, prelude::gas_oracle::{GasCategory, GasOracle}, types::{Address, Chain, U256}, }; @@ -70,9 +71,9 @@ pub async fn calc_pre_verification_gas( pub fn calc_static_pre_verification_gas(op: &UserOperation) -> U256 { let ov = GasOverheads::default(); - let packed = op.pack(); - let length_in_words = (packed.len() + 31) / 32; - let call_data_cost: U256 = packed + let encoded_op = op.clone().encode(); + let length_in_words = encoded_op.len() / 32; // size of packed user op is always a multiple of 32 bytes + let call_data_cost: U256 = encoded_op .iter() .map(|&x| { if x == 0 { diff --git a/crates/rundler/src/op_pool/mempool/mod.rs b/crates/rundler/src/op_pool/mempool/mod.rs index 0d5e94960..b2b971659 100644 --- a/crates/rundler/src/op_pool/mempool/mod.rs +++ b/crates/rundler/src/op_pool/mempool/mod.rs @@ -163,8 +163,11 @@ impl PoolOperation { }) } - pub fn size(&self) -> usize { - self.uo.pack().len() + pub fn mem_size(&self) -> usize { + std::mem::size_of::() + + self.uo.mem_size() + + std::mem::size_of::>() + + self.entities_needing_stake.len() * std::mem::size_of::() } fn entity_address(&self, entity: EntityType) -> Option
{ diff --git a/crates/rundler/src/op_pool/mempool/pool.rs b/crates/rundler/src/op_pool/mempool/pool.rs index ec3147326..5c1f9a9d5 100644 --- a/crates/rundler/src/op_pool/mempool/pool.rs +++ b/crates/rundler/src/op_pool/mempool/pool.rs @@ -337,7 +337,7 @@ impl OrderedPoolOperation { } fn size(&self) -> usize { - self.po.size() + self.po.mem_size() } } @@ -640,7 +640,7 @@ mod tests { } assert_eq!(pool.address_count(sender), 1); - assert_eq!(pool.pool_size, po1.size()); + assert_eq!(pool.pool_size, po1.mem_size()); } #[test] @@ -663,7 +663,7 @@ mod tests { assert_eq!(pool.address_count(sender), 1); assert_eq!(pool.address_count(paymaster1), 0); assert_eq!(pool.address_count(paymaster2), 1); - assert_eq!(pool.pool_size, po2.size()); + assert_eq!(pool.pool_size, po2.mem_size()); } #[test] @@ -688,7 +688,7 @@ mod tests { chain_id: 1, max_userops_per_sender: 16, min_replacement_fee_increase_percentage: 10, - max_size_of_pool_bytes: 20 * size_of_op(), + max_size_of_pool_bytes: 20 * mem_size_of_op(), blocklist: None, allowlist: None, precheck_settings: precheck::Settings::default(), @@ -697,8 +697,8 @@ mod tests { } } - fn size_of_op() -> usize { - create_op(Address::random(), 1, 1).size() + fn mem_size_of_op() -> usize { + create_op(Address::random(), 1, 1).mem_size() } fn create_op(sender: Address, nonce: usize, max_fee_per_gas: usize) -> PoolOperation { diff --git a/crates/rundler/src/rpc/eth/estimation.rs b/crates/rundler/src/rpc/eth/estimation.rs index 15e2cb400..6fc164f38 100644 --- a/crates/rundler/src/rpc/eth/estimation.rs +++ b/crates/rundler/src/rpc/eth/estimation.rs @@ -486,13 +486,13 @@ mod tests { let u_o = user_op.max_fill(&settings); - let u_o_packed = u_o.pack(); - let length_in_words = (u_o_packed.len() + 31) / 32; + let u_o_encoded = u_o.encode(); + let length_in_words = (u_o_encoded.len() + 31) / 32; //computed by mapping through the calldata bytes //and adding to the value either 4 or 16 depending //if the byte is non-zero - let call_data_cost = 4316; + let call_data_cost = 3936; let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + call_data_cost @@ -527,13 +527,13 @@ mod tests { let u_o = user_op.max_fill(&settings); - let u_o_packed = u_o.pack(); - let length_in_words = (u_o_packed.len() + 31) / 32; + let u_o_encoded = u_o.encode(); + let length_in_words = (u_o_encoded.len() + 31) / 32; //computed by mapping through the calldata bytes //and adding to the value either 4 or 16 depending //if the byte is non-zero - let call_data_cost = 4316; + let call_data_cost = 3936; let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + call_data_cost @@ -570,13 +570,13 @@ mod tests { let u_o = user_op.max_fill(&settings); - let u_o_packed = u_o.pack(); - let length_in_words = (u_o_packed.len() + 31) / 32; + let u_o_encoded: Bytes = u_o.encode().into(); + let length_in_words = (u_o_encoded.len() + 31) / 32; //computed by mapping through the calldata bytes //and adding to the value either 4 or 16 depending //if the byte is non-zero - let call_data_cost = 4316; + let call_data_cost = 3936; let result = U256::from(FIXED) / U256::from(BUNDLE_SIZE) + call_data_cost @@ -1131,7 +1131,7 @@ mod tests { let estimation = estimator.estimate_op_gas(user_op).await.unwrap(); // this number uses the same logic as the pre_verification tests - assert_eq!(estimation.pre_verification_gas, U256::from(43656)); + assert_eq!(estimation.pre_verification_gas, U256::from(43296)); // 30000 GAS_FEE_TRANSER_COST increased by default 10% assert_eq!(estimation.verification_gas_limit, U256::from(33000)); diff --git a/crates/types/src/user_operation.rs b/crates/types/src/user_operation.rs index 8189f58d2..2c310fce6 100644 --- a/crates/types/src/user_operation.rs +++ b/crates/types/src/user_operation.rs @@ -10,6 +10,9 @@ use crate::{ UserOperation, }; +/// Number of bytes in the fixed size portion of an ABI encoded user operation +const PACKED_USER_OPERATION_FIXED_LEN: usize = 480; + /// Unique identifier for a user operation from a given sender #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct UserOperationId { @@ -24,7 +27,7 @@ impl UserOperation { /// It does not include the signature field. pub fn op_hash(&self, entry_point: Address, chain_id: u64) -> H256 { keccak256(encode(&[ - Token::FixedBytes(keccak256(self.pack()).to_vec()), + Token::FixedBytes(keccak256(self.pack_for_hash()).to_vec()), Token::Address(entry_point), Token::Uint(chain_id.into()), ])) @@ -60,8 +63,26 @@ impl UserOperation { } } - /// Packs the user operation into a byte array, consistent with the entrypoint contract's expectation - pub fn pack(&self) -> Bytes { + /// Efficient calculation of the size of a packed user operation + pub fn abi_encoded_size(&self) -> usize { + PACKED_USER_OPERATION_FIXED_LEN + + pad_len(&self.init_code) + + pad_len(&self.call_data) + + pad_len(&self.paymaster_and_data) + + pad_len(&self.signature) + } + + /// Calculates the size of the user operation in memory + pub fn mem_size(&self) -> usize { + std::mem::size_of::() + + self.init_code.len() + + self.call_data.len() + + self.paymaster_and_data.len() + + self.signature.len() + } + + /// Gets the byte array representation of the user operation to be used in the signature + pub fn pack_for_hash(&self) -> Bytes { let hash_init_code = keccak256(self.init_code.clone()); let hash_call_data = keccak256(self.call_data.clone()); let hash_paymaster_and_data = keccak256(self.paymaster_and_data.clone()); @@ -100,9 +121,19 @@ impl UserOperation { } } +/// Calculates the size a byte array padded to the next largest multiple of 32 +fn pad_len(b: &Bytes) -> usize { + (b.len() + 31) & !31 +} + #[cfg(test)] mod tests { - use ethers::types::{Bytes, U256}; + use std::str::FromStr; + + use ethers::{ + abi::AbiEncode, + types::{Bytes, U256}, + }; use super::*; @@ -229,4 +260,28 @@ mod tests { .unwrap() ); } + + #[test] + fn test_abi_encoded_size() { + let user_operation = UserOperation { + sender: "0xe29a7223a7e040d70b5cd460ef2f4ac6a6ab304d" + .parse() + .unwrap(), + nonce: U256::from_dec_str("3937668929043450082210854285941660524781292117276598730779").unwrap(), + init_code: Bytes::default(), + call_data: Bytes::from_str("0x5194544700000000000000000000000058440a3e78b190e5bd07905a08a60e30bb78cb5b000000000000000000000000000000000000000000000000000009184e72a000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), + call_gas_limit: 40_960.into(), + verification_gas_limit: 75_099.into(), + pre_verification_gas: 46_330.into(), + max_fee_per_gas: 105_000_000.into(), + max_priority_fee_per_gas: 105_000_000.into(), + paymaster_and_data: Bytes::from_str("0xc03aac639bb21233e0139381970328db8bceeb6700006508996f000065089a9b0000000000000000000000000000000000000000ca7517be4e51ca2cde69bc44c4c3ce00ff7f501ce4ee1b3c6b2a742f579247292e4f9a672522b15abee8eaaf1e1487b8e3121d61d42ba07a47f5ccc927aa7eb61b").unwrap(), + signature: Bytes::from_str("0x00000000f8a0655423f2dfbb104e0ff906b7b4c64cfc12db0ac5ef0fb1944076650ce92a1a736518e5b6cd46c6ff6ece7041f2dae199fb4c8e7531704fbd629490b712dc1b").unwrap(), + }; + + assert_eq!( + user_operation.clone().encode().len(), + user_operation.abi_encoded_size() + ); + } }