Skip to content

Commit

Permalink
fix(7702): add max_fill, random_fill, and use for gas estimation (#991)
Browse files Browse the repository at this point in the history
  • Loading branch information
andysim3d authored Feb 4, 2025
1 parent 2c64f9f commit a997527
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 82 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ alloy-sol-types = "0.8.15"
# alloy
alloy-consensus = "0.7.3"
alloy-contract = "0.7.3"
alloy-eips = "0.7.3"
alloy-eips = { version = "0.7.3", features = ["k256","serde", "std"] }
alloy-json-rpc = "0.7.3"
alloy-provider = { version = "0.7.3", default-features = false, features = ["reqwest", "reqwest-rustls-tls"] }
alloy-rpc-client = "0.7.3"
alloy-rpc-types-eth = "0.7.3"
alloy-rpc-types-trace = "0.7.3"
alloy-signer = "0.7.3"
alloy-signer-aws = "0.7.3"
alloy-signer-local = "0.7.3"
alloy-signer-local = { version = "0.7.3" }
alloy-transport = "0.7.3"
alloy-transport-http = { version = "0.7.3", default-features = false, features = ["reqwest", "reqwest-rustls-tls"] }
alloy-network = { version = "0.7.3" }
Expand Down
3 changes: 3 additions & 0 deletions crates/pool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ url.workspace = true
mockall = { workspace = true, optional = true }

[dev-dependencies]
alloy-eips.workspace = true
alloy-signer.workspace = true
alloy-signer-local.workspace = true
mockall.workspace = true
reth-tasks.workspace = true
rundler-provider = { workspace = true, features = ["test-utils"] }
Expand Down
3 changes: 2 additions & 1 deletion crates/pool/src/mempool/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ where
}

pub(crate) fn check_eip7702(&self, uo: &UserOperationVariant) -> MempoolResult<()> {
if uo.authorization_tuple().is_some() {
if let Some(auth) = uo.authorization_tuple() {
if self.config.support_7702 {
auth.validate()?;
Ok(())
} else {
Err(MempoolError::EIPNotSupported(
Expand Down
39 changes: 37 additions & 2 deletions crates/pool/src/mempool/uo_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,9 +942,11 @@ struct UoPoolMetrics {

#[cfg(test)]
mod tests {
use std::{collections::HashMap, vec};
use std::{collections::HashMap, str::FromStr, vec};

use alloy_primitives::{uint, Bytes};
use alloy_signer::SignerSync;
use alloy_signer_local::PrivateKeySigner;
use mockall::Sequence;
use rundler_provider::{
DepositInfo, ExecutionResult, MockDAGasOracleSync, MockEntryPointV0_6, MockEvmProvider,
Expand All @@ -969,7 +971,6 @@ mod tests {
chain::{BalanceUpdate, MinedOp},
mempool::{PaymasterConfig, ReputationParams},
};

const THROTTLE_SLACK: u64 = 5;
const BAN_SLACK: u64 = 10;

Expand Down Expand Up @@ -1882,6 +1883,40 @@ mod tests {
assert!(pool
.add_operation(OperationOrigin::Local, op.clone().op)
.await
.is_err());
}
{
config.support_7702 = true;
let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
let signer: PrivateKeySigner = PrivateKeySigner::from_str(private_key).unwrap();
let authorization = alloy_eips::eip7702::Authorization {
chain_id: 11011,
address: Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap(),
nonce: 1,
};
let signature = signer
.sign_hash_sync(&authorization.signature_hash())
.unwrap();
let signed_authorization = authorization.into_signed(signature);
let signed_op = create_op_from_op_v0_6(UserOperation {
call_gas_limit: 50_000,
max_fee_per_gas: 0,
max_priority_fee_per_gas: 0,
authorization_tuple: Some(Eip7702Auth {
address: signed_authorization.address,
chain_id: signed_authorization.chain_id,
nonce: signed_authorization.nonce,
y_parity: signed_authorization.y_parity(),
r: signed_authorization.r(),
s: signed_authorization.s(),
}),
..Default::default()
});

let pool = create_pool_with_config(config.clone(), vec![signed_op.clone()]);
assert!(pool
.add_operation(OperationOrigin::Local, signed_op.clone().op)
.await
.is_ok());
}
}
Expand Down
33 changes: 21 additions & 12 deletions crates/pool/src/server/remote/protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,10 @@ impl From<&UserOperationVariant> for UserOperation {

impl From<&v0_6::UserOperation> for UserOperation {
fn from(op: &v0_6::UserOperation) -> Self {
let authorization_tuple =
op.authorization_tuple
.as_ref()
.map(|authorization| AuthorizationTuple {
chain_id: authorization.chain_id,
address: authorization.address.to_proto_bytes(),
nonce: authorization.nonce,
y_parity: authorization.y_parity.into(),
r: authorization.r.to_proto_bytes(),
s: authorization.s.to_proto_bytes(),
});
let authorization_tuple = op
.authorization_tuple
.as_ref()
.map(|authorization| AuthorizationTuple::from(authorization.clone()));
let op = UserOperationV06 {
sender: op.sender.to_proto_bytes(),
nonce: op.nonce.to_proto_bytes(),
Expand Down Expand Up @@ -95,6 +88,19 @@ impl From<AuthorizationTuple> for Eip7702Auth {
}
}
}

impl From<Eip7702Auth> for AuthorizationTuple {
fn from(value: Eip7702Auth) -> Self {
AuthorizationTuple {
chain_id: value.chain_id,
address: value.address.to_proto_bytes(),
nonce: value.nonce,
y_parity: value.y_parity.into(),
r: value.r.to_proto_bytes(),
s: value.s.to_proto_bytes(),
}
}
}
impl TryUoFromProto<UserOperationV06> for v0_6::UserOperation {
fn try_uo_from_proto(
op: UserOperationV06,
Expand Down Expand Up @@ -148,7 +154,10 @@ impl From<&v0_7::UserOperation> for UserOperation {
factory_data: op.factory_data.to_proto_bytes(),
entry_point: op.entry_point.to_proto_bytes(),
chain_id: op.chain_id,
authorization_tuple: None,
authorization_tuple: op
.authorization_tuple
.as_ref()
.map(|authorization| AuthorizationTuple::from(authorization.clone())),
};
UserOperation {
uo: Some(user_operation::Uo::V07(op)),
Expand Down
51 changes: 35 additions & 16 deletions crates/provider/src/alloy/entry_point/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,27 @@

use alloy_consensus::{transaction::SignableTransaction, TxEnvelope, TypedTransaction};
use alloy_primitives::{address, Address, Bytes, PrimitiveSignature, U256};
use alloy_provider::network::TransactionBuilder7702;
use alloy_rlp::Encodable;
use alloy_rpc_types_eth::TransactionRequest;
use rundler_types::authorization::Eip7702Auth;

pub(crate) mod v0_6;
pub(crate) mod v0_7;

fn max_bundle_transaction_data(to_address: Address, data: Bytes, gas_price: u128) -> Bytes {
fn max_bundle_transaction_data(
to_address: Address,
data: Bytes,
gas_price: u128,
au: Option<Eip7702Auth>,
) -> Bytes {
// Fill in max values for unknown or varying fields
let gas_price_ceil = gas_price.next_power_of_two() - 1; // max out bits of gas price, assume same power of 2
let gas_limit = 0xffffffff; // 4 bytes
let nonce = 0xffffffff; // 4 bytes
let chain_id = 0xffffffff; // 4 bytes

let tx = TransactionRequest::default()
let mut tx = TransactionRequest::default()
.from(address!("ffffffffffffffffffffffffffffffffffffffff"))
.to(to_address)
.gas_limit(gas_limit)
Expand All @@ -35,24 +42,36 @@ fn max_bundle_transaction_data(to_address: Address, data: Bytes, gas_price: u128
.value(U256::ZERO)
.input(data.into())
.nonce(nonce);
if let Some(auth) = au {
tx = tx.with_authorization_list(vec![auth.max_fill().into()])
}

// these conversions should not fail.
let ty = tx.build_typed_tx().unwrap();
let mut tx_1559 = match ty {
TypedTransaction::Eip1559(tx) => tx,
_ => {
panic!("transaction is not eip1559");
}
};

tx_1559.set_chain_id(chain_id);
match ty {
TypedTransaction::Eip1559(mut tx) => {
tx.set_chain_id(chain_id);
let tx_envelope: TxEnvelope = tx
.into_signed(PrimitiveSignature::new(U256::MAX, U256::MAX, false))
.into();
let mut encoded = vec![];
tx_envelope.encode(&mut encoded);

// use a max signature
let tx_envelope: TxEnvelope = tx_1559
.into_signed(PrimitiveSignature::new(U256::MAX, U256::MAX, false))
.into();
let mut encoded = vec![];
tx_envelope.encode(&mut encoded);
encoded.into()
}
TypedTransaction::Eip7702(mut tx) => {
tx.set_chain_id(chain_id);
let tx_envelope: TxEnvelope = tx
.into_signed(PrimitiveSignature::new(U256::MAX, U256::MAX, false))
.into();
let mut encoded = vec![];
tx_envelope.encode(&mut encoded);

encoded.into()
encoded.into()
}
_ => {
panic!("transaction is neither EIP-1559 nor EIP-7702");
}
}
}
8 changes: 2 additions & 6 deletions crates/provider/src/alloy/entry_point/v0_6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,14 @@ where
gas_price: u128,
) -> ProviderResult<(u128, DAGasUOData, DAGasBlockData)> {
let au = user_op.authorization_tuple();
let mut txn_request = self
let txn_request = self
.i_entry_point
.handleOps(vec![user_op.into()], Address::random())
.into_transaction_request();
if let Some(authorization) = au {
txn_request = txn_request.with_authorization_list(vec![authorization.into()]);
}

let data = txn_request.input.into_input().unwrap();

let bundle_data =
super::max_bundle_transaction_data(*self.i_entry_point.address(), data, gas_price);
super::max_bundle_transaction_data(*self.i_entry_point.address(), data, gas_price, au);

self.da_gas_oracle
.estimate_da_gas(bundle_data, *self.i_entry_point.address(), block, gas_price)
Expand Down
7 changes: 2 additions & 5 deletions crates/provider/src/alloy/entry_point/v0_7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,17 +320,14 @@ where
) -> ProviderResult<(u128, DAGasUOData, DAGasBlockData)> {
let au = user_op.authorization_tuple();

let mut txn_req = self
let txn_req = self
.i_entry_point
.handleOps(vec![user_op.pack()], Address::random())
.into_transaction_request();
if let Some(authorization_tuple) = au {
txn_req = txn_req.with_authorization_list(vec![authorization_tuple.into()]);
}

let data = txn_req.input.into_input().unwrap();
let bundle_data =
super::max_bundle_transaction_data(*self.i_entry_point.address(), data, gas_price);
super::max_bundle_transaction_data(*self.i_entry_point.address(), data, gas_price, au);

self.da_gas_oracle
.estimate_da_gas(bundle_data, *self.i_entry_point.address(), block, gas_price)
Expand Down
41 changes: 41 additions & 0 deletions crates/types/src/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use alloy_eips::eip7702::SignedAuthorization;
use alloy_primitives::{Address, U256};
use rundler_utils::random::random_bytes_array;
use serde::{Deserialize, Serialize};

/// authorization tuple for 7702 txn support
Expand Down Expand Up @@ -45,3 +46,43 @@ impl From<Eip7702Auth> for alloy_eips::eip7702::SignedAuthorization {
SignedAuthorization::new_unchecked(authorization, value.y_parity, value.r, value.s)
}
}

impl Eip7702Auth {
/// Genreate a random filled Eip7702Auth entity.
pub fn random_fill(&self) -> Eip7702Auth {
Self {
chain_id: self.chain_id,
address: self.address,
nonce: u64::from_le_bytes(random_bytes_array::<8, 4>()),
y_parity: 27,
r: U256::from_le_bytes(random_bytes_array::<64, 32>()),
s: U256::from_le_bytes(random_bytes_array::<64, 32>()),
}
}
/// Generate a maxfilled Eip7702Auth entity.
pub fn max_fill(&self) -> Eip7702Auth {
Self {
chain_id: self.chain_id,
address: self.address,
nonce: u64::MAX,
y_parity: 27,
r: U256::MAX,
s: U256::MAX,
}
}

/// validate a Eip7702Auth's signature.
pub fn validate(&self) -> anyhow::Result<()> {
let signed_auth = SignedAuthorization::from(self.clone());
match signed_auth.recover_authority() {
Ok(address) => {
if address == self.address {
Ok(())
} else {
Err(anyhow::anyhow!("Invalid signature for EIP-7702 authorization tuple - expected {:?} recovered {:?}", self.address, address))
}
}
Err(e) => Err(anyhow::anyhow!(e.to_string())),
}
}
}
Loading

0 comments on commit a997527

Please sign in to comment.