Skip to content

Commit

Permalink
feat: support 7702 txn with userOps (#923)
Browse files Browse the repository at this point in the history
  • Loading branch information
andysim3d authored Dec 16, 2024
1 parent 5e77dfe commit 4463b96
Show file tree
Hide file tree
Showing 32 changed files with 549 additions and 81 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.

45 changes: 29 additions & 16 deletions crates/builder/src/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,40 @@ pub(crate) trait Signer: Send + Sync {
.nonce
.context("nonce should be set when transaction is filled")?;

let TypedTransaction::Eip1559(mut tx_1559) = tx
.build_typed_tx()
.expect("should build eip1559 transaction")
else {
bail!("transaction is not eip1559");
};
match tx.build_typed_tx().expect("unsupported transaction.") {
TypedTransaction::Eip1559(mut tx_1559) => {
tx_1559.set_chain_id(self.chain_id());
let tx_hash = tx_1559.signature_hash();

let signature = self
.sign_hash(&tx_hash)
.await
.context("should sign transaction before sending")?;

let signed: TxEnvelope = tx_1559.into_signed(signature).into();

tx_1559.set_chain_id(self.chain_id());
let tx_hash = tx_1559.signature_hash();
let mut encoded = vec![];
signed.encode_2718(&mut encoded);

let signature = self
.sign_hash(&tx_hash)
.await
.context("should sign transaction before sending")?;
return Ok((encoded.into(), nonce));
}
TypedTransaction::Eip7702(mut tx_7702) => {
tx_7702.set_chain_id(self.chain_id());
let tx_hash = tx_7702.signature_hash();
let signature = self
.sign_hash(&tx_hash)
.await
.context("should sign transaction before sending")?;

let signed: TxEnvelope = tx_1559.into_signed(signature).into();
let signed: TxEnvelope = tx_7702.into_signed(signature).into();

let mut encoded = vec![];
signed.encode_2718(&mut encoded);
let mut encoded = vec![];
signed.encode_2718(&mut encoded);

Ok((encoded.into(), nonce))
return Ok((encoded.into(), nonce));
}
_ => bail!("transaction is either eip1559 nor eip7702."),
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions crates/pool/proto/op_pool/op_pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ message UserOperation {
}
}

// Protocol Buffer representation of an 7702 authorization tuple. See the official
// specification at https://eips.ethereum.org/EIPS/eip-7702
message AuthorizationTuple {
uint64 chain_id = 1;
uint64 nonce = 2;
bytes address = 3;
// signed authorization tuple.
uint32 y_parity = 4;
bytes r = 5;
bytes s = 6;
}

// Protocol Buffer representation of an ERC-4337 UserOperation. See the official
// specification at https://eips.ethereum.org/EIPS/eip-4337#definitions
message UserOperationV06 {
Expand Down Expand Up @@ -52,6 +64,8 @@ message UserOperationV06 {
bytes paymaster_and_data = 10;
// Signature over the hash of the packed representation of the user operation
bytes signature = 11;
// authorization tuple for 7702 txns
AuthorizationTuple authorization_tuple = 12;
}

message UserOperationV07 {
Expand Down Expand Up @@ -90,6 +104,9 @@ message UserOperationV07 {
// Extra data to compute the hash of the user operation
bytes entry_point = 16;
uint64 chain_id = 17;

// authorization tuple for 7702 txns
AuthorizationTuple authorization_tuple = 18;
}

enum EntityType {
Expand Down Expand Up @@ -592,6 +609,7 @@ message PrecheckViolationError {
MaxFeePerGasTooLow max_fee_per_gas_too_low = 10;
MaxPriorityFeePerGasTooLow max_priority_fee_per_gas_too_low = 11;
CallGasLimitTooLow call_gas_limit_too_low = 12;
FactoryMustBeEmpty factory_must_be_empty = 13;
}
}

Expand Down Expand Up @@ -659,6 +677,10 @@ message CallGasLimitTooLow {
bytes min_gas_limit = 2;
}

message FactoryMustBeEmpty{
bytes factory_address = 1;
}

// SIMULATION VIOLATIONS
message SimulationViolationError {
oneof violation {
Expand Down
22 changes: 16 additions & 6 deletions crates/pool/src/server/remote/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ use super::protos::{
CallGasLimitEfficiencyTooLow, CallGasLimitTooLow, CallHadValue, CalledBannedEntryPointMethod,
CodeHashChanged, DidNotRevert, DiscardedOnInsertError, Entity, EntityThrottledError,
EntityType, EntryPointRevert, ExistingSenderWithInitCode, FactoryCalledCreate2Twice,
FactoryIsNotContract, InvalidAccountSignature, InvalidPaymasterSignature, InvalidSignature,
InvalidStorageAccess, InvalidTimeRange, MaxFeePerGasTooLow, MaxOperationsReachedError,
MaxPriorityFeePerGasTooLow, MempoolError as ProtoMempoolError, MultipleRolesViolation,
NotStaked, OperationAlreadyKnownError, OperationDropTooSoon, OperationRevert, OutOfGas,
PanicRevert, PaymasterBalanceTooLow, PaymasterDepositTooLow, PaymasterIsNotContract,
PreOpGasLimitEfficiencyTooLow, PreVerificationGasTooLow,
FactoryIsNotContract, FactoryMustBeEmpty, InvalidAccountSignature, InvalidPaymasterSignature,
InvalidSignature, InvalidStorageAccess, InvalidTimeRange, MaxFeePerGasTooLow,
MaxOperationsReachedError, MaxPriorityFeePerGasTooLow, MempoolError as ProtoMempoolError,
MultipleRolesViolation, NotStaked, OperationAlreadyKnownError, OperationDropTooSoon,
OperationRevert, OutOfGas, PanicRevert, PaymasterBalanceTooLow, PaymasterDepositTooLow,
PaymasterIsNotContract, PreOpGasLimitEfficiencyTooLow, PreVerificationGasTooLow,
PrecheckViolationError as ProtoPrecheckViolationError, ReplacementUnderpricedError,
SenderAddressUsedAsAlternateEntity, SenderFundsTooLow, SenderIsNotContractAndNoInitCode,
SimulationViolationError as ProtoSimulationViolationError, TotalGasLimitTooHigh,
Expand Down Expand Up @@ -364,6 +364,13 @@ impl From<PrecheckViolation> for ProtoPrecheckViolationError {
},
)),
},
PrecheckViolation::FactoryMustBeEmpty(addr) => ProtoPrecheckViolationError {
violation: Some(precheck_violation_error::Violation::FactoryMustBeEmpty(
FactoryMustBeEmpty {
factory_address: addr.to_proto_bytes(),
},
)),
},
}
}
}
Expand Down Expand Up @@ -433,6 +440,9 @@ impl TryFrom<ProtoPrecheckViolationError> for PrecheckViolation {
from_bytes(&e.min_gas_limit)?,
)
}
Some(precheck_violation_error::Violation::FactoryMustBeEmpty(e)) => {
PrecheckViolation::FactoryMustBeEmpty(from_bytes(&e.factory_address)?)
}
None => {
bail!("unknown proto mempool precheck violation")
}
Expand Down
46 changes: 45 additions & 1 deletion crates/pool/src/server/remote/protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use alloy_primitives::{Address, B256};
use anyhow::{anyhow, Context};
use rundler_task::grpc::protos::{from_bytes, ConversionError, ToProtoBytes};
use rundler_types::{
authorization::Authorization,
chain::ChainSpec,
da::{
BedrockDAGasUOData as RundlerBedrockDAGasUOData, DAGasUOData as RundlerDAGasUOData,
Expand All @@ -25,7 +26,8 @@ use rundler_types::{
Reputation as PoolReputation, ReputationStatus as PoolReputationStatus,
StakeStatus as RundlerStakeStatus,
},
v0_6, v0_7, Entity as RundlerEntity, EntityInfos, EntityType as RundlerEntityType,
v0_6::{self, ExtendedUserOperation},
v0_7, Entity as RundlerEntity, EntityInfos, EntityType as RundlerEntityType,
EntityUpdate as RundlerEntityUpdate, EntityUpdateType as RundlerEntityUpdateType,
StakeInfo as RundlerStakeInfo, UserOperationVariant, ValidTimeRange,
};
Expand All @@ -46,6 +48,17 @@ 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 op = UserOperationV06 {
sender: op.sender.to_proto_bytes(),
nonce: op.nonce.to_proto_bytes(),
Expand All @@ -58,6 +71,7 @@ impl From<&v0_6::UserOperation> for UserOperation {
max_priority_fee_per_gas: op.max_priority_fee_per_gas.to_proto_bytes(),
paymaster_and_data: op.paymaster_and_data.to_proto_bytes(),
signature: op.signature.to_proto_bytes(),
authorization_tuple,
};
UserOperation {
uo: Some(user_operation::Uo::V06(op)),
Expand All @@ -69,11 +83,28 @@ pub trait TryUoFromProto<T>: Sized {
fn try_uo_from_proto(value: T, chain_spec: &ChainSpec) -> Result<Self, ConversionError>;
}

impl From<AuthorizationTuple> for Authorization {
fn from(value: AuthorizationTuple) -> Self {
Authorization {
chain_id: value.chain_id,
address: from_bytes(&value.address).unwrap_or_default(),
nonce: value.nonce,
y_parity: value.y_parity as u8,
r: from_bytes(&value.r).unwrap_or_default(),
s: from_bytes(&value.s).unwrap_or_default(),
}
}
}
impl TryUoFromProto<UserOperationV06> for v0_6::UserOperation {
fn try_uo_from_proto(
op: UserOperationV06,
chain_spec: &ChainSpec,
) -> Result<Self, ConversionError> {
let authorization_tuple = op
.authorization_tuple
.as_ref()
.map(|authorization| Authorization::from(authorization.clone()));

Ok(v0_6::UserOperationBuilder::new(
chain_spec,
v0_6::UserOperationRequiredFields {
Expand All @@ -89,6 +120,9 @@ impl TryUoFromProto<UserOperationV06> for v0_6::UserOperation {
paymaster_and_data: op.paymaster_and_data.into(),
signature: op.signature.into(),
},
ExtendedUserOperation {
authorization_tuple,
},
)
.build())
}
Expand All @@ -114,6 +148,7 @@ 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,
};
UserOperation {
uo: Some(user_operation::Uo::V07(op)),
Expand All @@ -126,6 +161,11 @@ impl TryUoFromProto<UserOperationV07> for v0_7::UserOperation {
op: UserOperationV07,
chain_spec: &ChainSpec,
) -> Result<Self, ConversionError> {
let authorization_tuple = op
.authorization_tuple
.as_ref()
.map(|authorization| Authorization::from(authorization.clone()));

let mut builder = v0_7::UserOperationBuilder::new(
chain_spec,
v0_7::UserOperationRequiredFields {
Expand All @@ -150,6 +190,10 @@ impl TryUoFromProto<UserOperationV07> for v0_7::UserOperation {
);
}

if authorization_tuple.is_some() {
builder = builder.authorization_tuple(authorization_tuple);
}

if !op.factory.is_empty() {
builder = builder.factory(from_bytes(&op.factory)?, op.factory_data.into());
}
Expand Down
1 change: 1 addition & 0 deletions crates/provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rundler-utils.workspace = true

alloy-consensus.workspace = true
alloy-contract.workspace = true
alloy-eips.workspace = true
alloy-json-rpc.workspace = true
alloy-primitives = { workspace = true, features = ["rand"] }
alloy-provider = { workspace = true, features = ["debug-api"] }
Expand Down
Loading

0 comments on commit 4463b96

Please sign in to comment.