transaction_limits - [devnet]
Manages configuration and validation for higher transaction limits based on staking.
Users can request multipliers to transaction limits (e..g, execution limit or IO limit) if they prove they control a significant stake:
- as a stake pool owner,
- as a delegated voter,
- as a delegation pool delegator. For example, one can request 2.5x on execution limits and 5x on IO limits. Multipliers are in basis points where 1 maps to 100, to support fractions.
The on-chain config stores a vector of tiers. Each tier maps multiplier to the required minimum stake threshold. A smallest multiplier that is greater than or equal to the requested multiplier is chosen.
use 0x1::delegation_pool;use 0x1::stake;use 0x1::staking_config;use 0x1::system_addresses;Constants
Fee payer is not the delegated voter of the specified stake pool.
const ENOT_DELEGATED_VOTER: u64 = 3;No delegation pool exists at the specified address.
const EDELEGATION_POOL_NOT_FOUND: u64 = 4;Committed stake is insufficient for the requested multiplier tier.
const EINSUFFICIENT_STAKE: u64 = 5;Multiplier must be > 100 bps (> 1x).
const EINVALID_MULTIPLIER: u64 = 7;Requested multiplier is not available in any configured tier.
const EMULTIPLIER_NOT_AVAILABLE: u64 = 8;Fee payer is not the owner of the specified stake pool.
const ENOT_STAKE_POOL_OWNER: u64 = 2;No stake pool exists at the specified address.
const ESTAKE_POOL_NOT_FOUND: u64 = 1;Config tiers are not monotonically ordered.
const ETHRESHOLDS_NOT_MONOTONIC: u64 = 6;Min-stakes and multipliers vectors have different lengths.
const EVECTOR_LENGTH_MISMATCH: u64 = 9;Every multiplier must be less than or equal to this maximum (100x).
INVARIANT: must match Rust version checked by VM.
const MAX_MULTIPLIER_BPS: u64 = 10000;Every multiplier must be greater than this minimum (1x).
INVARIANT: must match Rust version checked by VM.
const MIN_MULTIPLIER_BPS: u64 = 100;Structs
TxnLimitTier
A single tier: the minimum committed stake required and the multiplier it unlocks.
struct TxnLimitTier has copy, drop, storeFields
-
min_stake: u64 -
multiplier_bps: u64
Functions
new_tier
Creates a new tier. Aborts if multiplier is not in (100, 10000] bps.
public fun new_tier(min_stake: u64, multiplier_bps: u64): transaction_limits::TxnLimitTierImplementation
public fun new_tier(min_stake: u64, multiplier_bps: u64): TxnLimitTier { assert!( multiplier_bps > MIN_MULTIPLIER_BPS && multiplier_bps <= MAX_MULTIPLIER_BPS, error::invalid_argument(EINVALID_MULTIPLIER) ); TxnLimitTier { min_stake, multiplier_bps }}validate_tiers
Aborts if:
- Minimum stake tiers are not monotonically increasing.
- Multiplier tiers are not strictly monotonically increasing.
fun validate_tiers(tiers: &vector<transaction_limits::TxnLimitTier>)Implementation
fun validate_tiers(tiers: &vector<TxnLimitTier>) { let i = 1; let len = tiers.length();
while (i < len) { let prev = &tiers[i - 1]; let curr = &tiers[i]; assert!( curr.min_stake >= prev.min_stake && curr.multiplier_bps > prev.multiplier_bps, error::invalid_argument(ETHRESHOLDS_NOT_MONOTONIC) ); i += 1; };}new_tiers
Builds a vector of tiers from inputs.
Aborts if:
- Minimum stakes and multipliers vectors have different lengths.
- Minimum stakes and multipliers vectors are not monotonically increasing.
- Multiplier is not valid (1x or below).
fun new_tiers(min_stakes: vector<u64>, multipliers_bps: vector<u64>): vector<transaction_limits::TxnLimitTier>Implementation
fun new_tiers(min_stakes: vector<u64>, multipliers_bps: vector<u64>) : vector<TxnLimitTier> { let len = min_stakes.length(); assert!( len == multipliers_bps.length(), error::invalid_argument(EVECTOR_LENGTH_MISMATCH) );
let tiers = vector[]; let i = 0; while (i < len) { tiers.push_back(new_tier(min_stakes[i], multipliers_bps[i])); i += 1; }; validate_tiers(&tiers);
tiers}find_min_stake_required
Finds the smallest tier whose multiplier is greater than or equal to the requested multiplier. Returns minimum stake correspondng to this tier.
Aborts if no tier can cover the request.
fun find_min_stake_required(tiers: &vector<transaction_limits::TxnLimitTier>, multiplier_bps: u64): u64Implementation
fun find_min_stake_required( tiers: &vector<TxnLimitTier>, multiplier_bps: u64): u64 { let (found, i) = tiers.find(|t| t.multiplier_bps >= multiplier_bps); assert!(found, error::invalid_argument(EMULTIPLIER_NOT_AVAILABLE)); tiers[i].min_stake}initialize
Only called during genesis.
public(friend) fun initialize(aptos_framework: &signer, execution_tiers: vector<transaction_limits::TxnLimitTier>, io_tiers: vector<transaction_limits::TxnLimitTier>)Implementation
friend fun initialize( aptos_framework: &signer, execution_tiers: vector<TxnLimitTier>, io_tiers: vector<TxnLimitTier>) { system_addresses::assert_aptos_framework(aptos_framework); validate_tiers(&execution_tiers); validate_tiers(&io_tiers);
move_to( aptos_framework, TxnLimitsConfig::V1 { execution_tiers, io_tiers } );}update_config
Governance-only: update stake thresholds and multipliers.
public fun update_config(aptos_framework: &signer, execution_min_stakes: vector<u64>, execution_multipliers_bps: vector<u64>, io_min_stakes: vector<u64>, io_multipliers_bps: vector<u64>)Implementation
public fun update_config( aptos_framework: &signer, execution_min_stakes: vector<u64>, execution_multipliers_bps: vector<u64>, io_min_stakes: vector<u64>, io_multipliers_bps: vector<u64>) acquires TxnLimitsConfig { system_addresses::assert_aptos_framework(aptos_framework);
let execution_tiers = new_tiers( execution_min_stakes, execution_multipliers_bps ); let io_tiers = new_tiers(io_min_stakes, io_multipliers_bps);
if (!exists<TxnLimitsConfig>(@aptos_framework)) { move_to( aptos_framework, TxnLimitsConfig::V1 { execution_tiers, io_tiers } ); } else { let config = &mut TxnLimitsConfig[@aptos_framework]; config.execution_tiers = execution_tiers; config.io_tiers = io_tiers; }}validate_enough_stake
Aborts if:
- Requested multipliers are not well-formed.
- Transaction limits config does not exist or there is no tier matching the requested multipliers.
- There is not enough stake to cover the minimum required amount.
fun validate_enough_stake(stake_amount: u64, multipliers: transaction_limits::RequestedMultipliers)Implementation
fun validate_enough_stake( stake_amount: u64, multipliers: RequestedMultipliers) acquires TxnLimitsConfig { let (execution_bps, io_bps) = match(multipliers) { RequestedMultipliers::V1 { execution_bps, io_bps } => (execution_bps, io_bps) }; assert!( execution_bps > MIN_MULTIPLIER_BPS && execution_bps <= MAX_MULTIPLIER_BPS, error::invalid_argument(EINVALID_MULTIPLIER) ); assert!( io_bps > MIN_MULTIPLIER_BPS && io_bps <= MAX_MULTIPLIER_BPS, error::invalid_argument(EINVALID_MULTIPLIER) );
let config = &TxnLimitsConfig[@aptos_framework]; let execution_threshold = find_min_stake_required(&config.execution_tiers, execution_bps); let io_threshold = find_min_stake_required(&config.io_tiers, io_bps);
assert!( stake_amount >= execution_threshold, error::permission_denied(EINSUFFICIENT_STAKE) ); assert!( stake_amount >= io_threshold, error::permission_denied(EINSUFFICIENT_STAKE) );}validate_high_txn_limits
Only called during prologue to validate that the fee payer qualifies for the requested limit multipliers.
public(friend) fun validate_high_txn_limits(fee_payer: address, request: transaction_limits::UserTxnLimitsRequest)Implementation
friend fun validate_high_txn_limits( fee_payer: address, request: UserTxnLimitsRequest) acquires TxnLimitsConfig { match(request) { StakePoolOwner { multipliers } => { assert!( stake::owner_cap_exists(fee_payer), error::permission_denied(ENOT_STAKE_POOL_OWNER) ); let pool_address = stake::get_pool_address_for_owner(fee_payer); let stake_amount = aptos_governance::get_voting_power(pool_address); validate_enough_stake(stake_amount, multipliers); }, DelegatedVoter { pool_address, multipliers } => { assert!( stake::stake_pool_exists(pool_address), error::not_found(ESTAKE_POOL_NOT_FOUND) ); assert!( fee_payer == stake::get_delegated_voter(pool_address), error::permission_denied(ENOT_DELEGATED_VOTER) ); let stake_amount = aptos_governance::get_voting_power(pool_address); validate_enough_stake(stake_amount, multipliers); }, DelegationPoolDelegator { pool_address, multipliers } => { assert!( delegation_pool::delegation_pool_exists(pool_address), error::not_found(EDELEGATION_POOL_NOT_FOUND) ); let (active, _, pending_inactive) = delegation_pool::get_stake( pool_address, fee_payer ); validate_enough_stake(active + pending_inactive, multipliers); } }}Specification
pragma verify = false;Enum Resource TxnLimitsConfig
On-chain configuration for higher transaction limits. Stores a vector of tiers for each dimension (e.g., execution, IO). Tiers are ordered monotonically by both minimum stakes and multipliers.
enum TxnLimitsConfig has keyVariants
V1
Fields
-
execution_tiers: vector<transaction_limits::TxnLimitTier> -
io_tiers: vector<transaction_limits::TxnLimitTier>
Enum RequestedMultipliers
Multipliers requested by the user, expressed in basis points (That is, 1x is 100, 2.5x is 250).
INVARIANT: must match Rust enum for BCS serialization.
enum RequestedMultipliers has copy, drop, storeVariants
V1
Fields
-
execution_bps: u64 -
io_bps: u64
Enum UserTxnLimitsRequest
Request for higher transaction limits, passed to the prologue. Carries the proof that the sender has enough stake.
INVARIANT: must match Rust enum for BCS serialization.
enum UserTxnLimitsRequest has copy, dropVariants
StakePoolOwner
Fields
-
multipliers: transaction_limits::RequestedMultipliers
DelegatedVoter
Fields
-
pool_address: address -
multipliers: transaction_limits::RequestedMultipliers
DelegationPoolDelegator
Fields
-
pool_address: address -
multipliers: transaction_limits::RequestedMultipliers