Skip to content

Commit

Permalink
fix(uo): fix user operation encoding and size calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-miao committed Sep 20, 2023
1 parent ee713a9 commit 198a16a
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 26 deletions.
9 changes: 6 additions & 3 deletions crates/pool/src/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,12 @@ impl PoolOperation {
})
}

/// Returns the size of the operation in bytes.
pub fn size(&self) -> usize {
self.uo.pack().len()
/// Compute the amount of memory the PoolOperation takes up.
pub fn mem_size(&self) -> usize {
std::mem::size_of::<Self>()
+ self.uo.mem_size()
+ std::mem::size_of::<Vec<EntityType>>()
+ self.entities_needing_stake.len() * std::mem::size_of::<EntityType>()
}

fn entity_address(&self, entity: EntityType) -> Option<Address> {
Expand Down
12 changes: 6 additions & 6 deletions crates/pool/src/mempool/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ impl OrderedPoolOperation {
}

fn size(&self) -> usize {
self.po.size()
self.po.mem_size()
}
}

Expand Down Expand Up @@ -664,7 +664,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]
Expand All @@ -687,7 +687,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]
Expand All @@ -712,12 +712,12 @@ 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(),
}
}

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 {
Expand Down
20 changes: 10 additions & 10 deletions crates/sim/src/estimation/estimation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,13 +472,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
Expand Down Expand Up @@ -513,13 +513,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
Expand Down Expand Up @@ -556,13 +556,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
Expand Down Expand Up @@ -1117,7 +1117,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));
Expand Down
7 changes: 4 additions & 3 deletions crates/sim/src/gas/gas.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

use ethers::{
abi::AbiEncode,
prelude::gas_oracle::GasCategory,
types::{Address, Chain, U256},
};
Expand Down Expand Up @@ -124,9 +125,9 @@ pub fn user_operation_max_gas_cost(uo: &UserOperation, chain_id: u64) -> U256 {

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 {
Expand Down
63 changes: 59 additions & 4 deletions crates/types/src/user_operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()),
]))
Expand Down Expand Up @@ -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>()
+ 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());
Expand Down Expand Up @@ -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::*;

Expand Down Expand Up @@ -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()
);
}
}

0 comments on commit 198a16a

Please sign in to comment.