Skip to content

Commit

Permalink
feat: add permission for relaxed fee checking
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Feb 25, 2025
1 parent 87ace2e commit 9cddce7
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 19 deletions.
20 changes: 17 additions & 3 deletions crates/builder/src/bundle_proposer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,19 @@ where
required_op_fees: GasFees,
) -> Option<PoolOperation> {
let op_hash = op.uo.hash();

let mut required_max_fee_per_gas = required_op_fees.max_fee_per_gas;
let mut required_max_priority_fee_per_gas = required_op_fees.max_priority_fee_per_gas;

if let Some(pct) = op.perms.underpriced_bundle_pct {
required_max_fee_per_gas = math::percent_ceil(required_max_fee_per_gas, pct);
required_max_priority_fee_per_gas =
math::percent_ceil(required_max_priority_fee_per_gas, pct);
}

// filter by fees
if op.uo.max_fee_per_gas() < required_op_fees.max_fee_per_gas
|| op.uo.max_priority_fee_per_gas() < required_op_fees.max_priority_fee_per_gas
if op.uo.max_fee_per_gas() < required_max_fee_per_gas
|| op.uo.max_priority_fee_per_gas() < required_max_priority_fee_per_gas
{
self.emit(BuilderEvent::skipped_op(
self.builder_tag.clone(),
Expand Down Expand Up @@ -455,12 +465,16 @@ where
}
};

let required_pvg = op.uo.required_pre_verification_gas(
let mut required_pvg = op.uo.required_pre_verification_gas(
&self.settings.chain_spec,
bundle_size,
required_da_gas,
);

if let Some(pct) = op.perms.underpriced_bundle_pct {
required_pvg = math::percent_ceil(required_pvg, pct);
}

if op.uo.pre_verification_gas() < required_pvg {
self.emit(BuilderEvent::skipped_op(
self.builder_tag.clone(),
Expand Down
2 changes: 2 additions & 0 deletions crates/pool/proto/op_pool/op_pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ message UserOperation {
message UserOperationPermissions {
bool trusted = 1;
optional uint64 max_allowed_in_pool_for_sender = 2;
optional uint32 underpriced_accept_pct = 3;
optional uint32 underpriced_bundle_pct = 4;
}

// Protocol Buffer representation of an 7702 authorization tuple. See the official
Expand Down
6 changes: 5 additions & 1 deletion crates/pool/src/mempool/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,15 @@ where
op.uo().extra_data_len(bundle_size),
);

let required_pvg = op.uo().required_pre_verification_gas(
let mut required_pvg = op.uo().required_pre_verification_gas(
&self.config.chain_spec,
bundle_size,
required_da_gas,
);
if let Some(pct) = op.po.perms.underpriced_bundle_pct {
required_pvg = math::percent_ceil(required_pvg, pct);
}

let actual_pvg = op.uo().pre_verification_gas();

if actual_pvg < required_pvg {
Expand Down
4 changes: 2 additions & 2 deletions crates/pool/src/mempool/uo_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ where
let precheck_ret = self
.pool_providers
.prechecker()
.check(&versioned_op, block_hash.into())
.check(&versioned_op, &perms, block_hash.into())
.await?;

// Only let ops with successful simulations through
Expand Down Expand Up @@ -2383,7 +2383,7 @@ mod tests {
.returning(|| Ok(FeeUpdate::default()));

for op in ops {
prechecker.expect_check().returning(move |_, _| {
prechecker.expect_check().returning(move |_, _, _| {
if let Some(error) = &op.precheck_error {
Err(PrecheckError::Violations(vec![error.clone()]))
} else {
Expand Down
4 changes: 4 additions & 0 deletions crates/pool/src/server/remote/protos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@ impl From<UserOperationPermissions> for RundlerUserOperationPermissions {
max_allowed_in_pool_for_sender: permissions
.max_allowed_in_pool_for_sender
.map(|c| c as usize),
underpriced_accept_pct: permissions.underpriced_accept_pct,
underpriced_bundle_pct: permissions.underpriced_bundle_pct,
}
}
}
Expand All @@ -590,6 +592,8 @@ impl From<RundlerUserOperationPermissions> for UserOperationPermissions {
max_allowed_in_pool_for_sender: permissions
.max_allowed_in_pool_for_sender
.map(|c| c as u64),
underpriced_accept_pct: permissions.underpriced_accept_pct,
underpriced_bundle_pct: permissions.underpriced_bundle_pct,
}
}
}
8 changes: 8 additions & 0 deletions crates/rpc/src/types/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,21 @@ pub(crate) struct RpcUserOperationPermissions {
/// The maximum sender allowed in the pool
#[serde(default)]
pub(crate) max_allowed_in_pool_for_sender: Option<U64>,
/// The allowed percentage of underpriced fees that is accepted into the pool
#[serde(default)]
pub(crate) underpriced_accept_pct: Option<U64>,
/// The allowed percentage of fees underpriced that is bundled
#[serde(default)]
pub(crate) underpriced_bundle_pct: Option<U64>,
}

impl FromRpc<RpcUserOperationPermissions> for UserOperationPermissions {
fn from_rpc(rpc: RpcUserOperationPermissions, _chain_spec: &ChainSpec) -> Self {
UserOperationPermissions {
trusted: rpc.trusted,
max_allowed_in_pool_for_sender: rpc.max_allowed_in_pool_for_sender.map(|c| c.to()),
underpriced_accept_pct: rpc.underpriced_accept_pct.map(|c| c.to()),
underpriced_bundle_pct: rpc.underpriced_bundle_pct.map(|c| c.to()),
}
}
}
43 changes: 30 additions & 13 deletions crates/sim/src/precheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// You should have received a copy of the GNU General Public License along with Rundler.
// If not, see https://www.gnu.org/licenses/.

use std::{marker::PhantomData, sync::RwLock};
use std::{cmp, marker::PhantomData, sync::RwLock};

use alloy_primitives::{Address, U256};
use anyhow::Context;
Expand All @@ -23,7 +23,7 @@ use rundler_types::{
chain::ChainSpec,
da::DAGasUOData,
pool::{MempoolError, PrecheckViolation},
GasFees, UserOperation,
GasFees, UserOperation, UserOperationPermissions,
};
use rundler_utils::math;
use tracing::instrument;
Expand Down Expand Up @@ -67,6 +67,7 @@ pub trait Prechecker: Send + Sync {
async fn check(
&self,
op: &Self::UO,
perms: &UserOperationPermissions,
block: BlockHashOrNumber,
) -> Result<PrecheckReturn, PrecheckError>;

Expand Down Expand Up @@ -174,12 +175,13 @@ where
async fn check(
&self,
op: &Self::UO,
perms: &UserOperationPermissions,
block: BlockHashOrNumber,
) -> Result<PrecheckReturn, PrecheckError> {
let async_data = self.load_async_data(op, block).await?;
let mut violations: Vec<PrecheckViolation> = vec![];
violations.extend(self.check_init_code(op, &async_data));
violations.extend(self.check_gas(op, &async_data));
violations.extend(self.check_gas(op, &async_data, perms));
violations.extend(self.check_payer(op, &async_data));
if !violations.is_empty() {
Err(violations)?
Expand Down Expand Up @@ -266,7 +268,12 @@ where
violations
}

fn check_gas(&self, op: &UO, async_data: &AsyncData) -> ArrayVec<PrecheckViolation, 6> {
fn check_gas(
&self,
op: &UO,
async_data: &AsyncData,
perms: &UserOperationPermissions,
) -> ArrayVec<PrecheckViolation, 6> {
let Settings {
max_verification_gas,
max_total_execution_gas,
Expand Down Expand Up @@ -299,10 +306,12 @@ where
// if preVerificationGas is dynamic, then allow for the percentage buffer
// and check if the preVerificationGas is at least the minimum.
if self.chain_spec.da_pre_verification_gas {
min_pre_verification_gas = math::percent(
min_pre_verification_gas,
let accept_pct = cmp::min(
perms.underpriced_accept_pct.unwrap_or(100),
self.settings.pre_verification_gas_accept_percent,
);

min_pre_verification_gas = math::percent_ceil(min_pre_verification_gas, accept_pct);
}
if op.pre_verification_gas() < min_pre_verification_gas {
violations.push(PrecheckViolation::PreVerificationGasTooLow(
Expand All @@ -312,10 +321,14 @@ where
}

// check that the max fee per gas and max priority fee per gas are at least the required fees
let min_base_fee = math::percent(base_fee, self.settings.base_fee_accept_percent);
let min_base_fee_accept_pct = cmp::min(
perms.underpriced_accept_pct.unwrap_or(100),
self.settings.base_fee_accept_percent,
);
let min_base_fee = math::percent_ceil(base_fee, min_base_fee_accept_pct);
let min_priority_fee = self.settings.priority_fee_mode.minimum_priority_fee(
base_fee,
self.settings.base_fee_accept_percent,
min_base_fee_accept_pct,
self.chain_spec.min_max_priority_fee_per_gas(),
self.settings.bundle_priority_fee_overhead_percent,
);
Expand Down Expand Up @@ -591,7 +604,11 @@ mod tests {
)
.build();

let res = prechecker.check_gas(&op, &get_test_async_data());
let res = prechecker.check_gas(
&op,
&get_test_async_data(),
&UserOperationPermissions::default(),
);

let total_gas_limit = op.gas_limit(&cs, Some(1));

Expand Down Expand Up @@ -685,7 +702,7 @@ mod tests {
)
.build();

let res = prechecker.check_gas(&op, &async_data);
let res = prechecker.check_gas(&op, &async_data, &UserOperationPermissions::default());
assert!(res.is_empty());
}

Expand Down Expand Up @@ -718,7 +735,7 @@ mod tests {
)
.build();

let res = prechecker.check_gas(&op, &async_data);
let res = prechecker.check_gas(&op, &async_data, &UserOperationPermissions::default());
let mut expected = ArrayVec::<PrecheckViolation, 6>::new();
expected.push(PrecheckViolation::MaxFeePerGasTooLow(
math::percent(5_000, settings.base_fee_accept_percent - 10),
Expand Down Expand Up @@ -763,7 +780,7 @@ mod tests {
)
.build();

let res = prechecker.check_gas(&op, &async_data);
let res = prechecker.check_gas(&op, &async_data, &UserOperationPermissions::default());
let mut expected = ArrayVec::<PrecheckViolation, 6>::new();
expected.push(PrecheckViolation::MaxPriorityFeePerGasTooLow(
mintip - 1,
Expand Down Expand Up @@ -805,7 +822,7 @@ mod tests {
)
.build();

let res = prechecker.check_gas(&op, &async_data);
let res = prechecker.check_gas(&op, &async_data, &UserOperationPermissions::default());
let mut expected = ArrayVec::<PrecheckViolation, 6>::new();
expected.push(PrecheckViolation::PreVerificationGasTooLow(
math::percent(1_000, settings.pre_verification_gas_accept_percent - 10),
Expand Down
4 changes: 4 additions & 0 deletions crates/types/src/user_operation/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ pub struct UserOperationPermissions {
pub trusted: bool,
/// The maximum number of user operations allowed for a sender in the mempool
pub max_allowed_in_pool_for_sender: Option<usize>,
/// The allowed percentage of underpriced fees that is accepted into the pool
pub underpriced_accept_pct: Option<u32>,
/// The allowed percentage of fees underpriced that is bundled
pub underpriced_bundle_pct: Option<u32>,
}
13 changes: 13 additions & 0 deletions crates/utils/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ where
(n * T::from(percent)) / T::from(100)
}

/// Take a percentage of a number, rounding up
pub fn percent_ceil<T>(n: T, percent: u32) -> T
where
T: Add<Output = T> + Mul<Output = T> + Div<Output = T> + From<u32>,
{
(n * T::from(percent) + T::from(99)) / T::from(100)
}

#[cfg(test)]
mod tests {
use alloy_primitives::U256;
Expand Down Expand Up @@ -106,4 +114,9 @@ mod tests {
fn test_percent() {
assert_eq!(percent(3123_u32, 10), 312);
}

#[test]
fn test_percent_ceil() {
assert_eq!(percent_ceil(3123_u32, 10), 313);
}
}

0 comments on commit 9cddce7

Please sign in to comment.