Skip to content

transaction_limits - [testnet]

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 in a stake pool that is currently in the active validator set:

  • 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 expressed as percent of the base limit where 100 is 1x, 250 is 2.5x.

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 (> 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;

Stake pool is not in the current-epoch validator set.

const EPOOL_NOT_IN_VALIDATOR_SET: u64 = 9;

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 = 10;

Every multiplier must be less than or equal to this maximum (100x).

INVARIANT: must match Rust version checked by VM.

const MAX_MULTIPLIER_PERCENT: u64 = 10000;

Every multiplier must be greater than this minimum (1x).

INVARIANT: must match Rust version checked by VM.

const MIN_MULTIPLIER_PERCENT: u64 = 100;

Structs

TxnLimitTier

A single tier: the minimum committed stake required and the multiplier it unlocks.

struct TxnLimitTier has copy, drop, store
Fields
min_stake: u64
multiplier_percent: u64

Functions

new_tier

Creates a new tier. Aborts if multiplier is not in (100, 10000].

public(friend) fun new_tier(min_stake: u64, multiplier_percent: u64): transaction_limits::TxnLimitTier
Implementation
friend fun new_tier(min_stake: u64, multiplier_percent: u64): TxnLimitTier {
assert!(
multiplier_percent > MIN_MULTIPLIER_PERCENT
&& multiplier_percent <= MAX_MULTIPLIER_PERCENT,
error::invalid_argument(EINVALID_MULTIPLIER)
);
TxnLimitTier { min_stake, multiplier_percent }
}

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_percent > prev.multiplier_percent,
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_percent: vector<u64>): vector<transaction_limits::TxnLimitTier>
Implementation
fun new_tiers(min_stakes: vector<u64>, multipliers_percent: vector<u64>)
: vector<TxnLimitTier> {
let len = min_stakes.length();
assert!(
len == multipliers_percent.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_percent[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.

Implemnetation note: Tier count is small in practice, so using linear search here, which is cheaper and currently faster than a binary search in Move bytecode.

fun find_min_stake_required(tiers: &vector<transaction_limits::TxnLimitTier>, multiplier_percent: u64): u64
Implementation
fun find_min_stake_required(
tiers: &vector<TxnLimitTier>, multiplier_percent: u64
): u64 {
let (found, i) = tiers.find(|t| t.multiplier_percent >= multiplier_percent);
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_percent: vector<u64>, io_min_stakes: vector<u64>, io_multipliers_percent: vector<u64>)
Implementation
public fun update_config(
aptos_framework: &signer,
execution_min_stakes: vector<u64>,
execution_multipliers_percent: vector<u64>,
io_min_stakes: vector<u64>,
io_multipliers_percent: vector<u64>
) {
system_addresses::assert_aptos_framework(aptos_framework);
let execution_tiers = new_tiers(
execution_min_stakes, execution_multipliers_percent
);
let io_tiers = new_tiers(io_min_stakes, io_multipliers_percent);
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
) {
let (execution_multiplier_percent, io_multiplier_percent) =
match(multipliers) {
RequestedMultipliers::V1 {
execution_multiplier_percent,
io_multiplier_percent
} => (execution_multiplier_percent, io_multiplier_percent)
};
assert!(
execution_multiplier_percent > MIN_MULTIPLIER_PERCENT
&& execution_multiplier_percent <= MAX_MULTIPLIER_PERCENT,
error::invalid_argument(EINVALID_MULTIPLIER)
);
assert!(
io_multiplier_percent > MIN_MULTIPLIER_PERCENT
&& io_multiplier_percent <= MAX_MULTIPLIER_PERCENT,
error::invalid_argument(EINVALID_MULTIPLIER)
);
let config = &TxnLimitsConfig[@aptos_framework];
let execution_threshold =
find_min_stake_required(&config.execution_tiers, execution_multiplier_percent);
let io_threshold =
find_min_stake_required(&config.io_tiers, io_multiplier_percent);
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
) {
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);
assert!(
stake::is_current_epoch_validator(pool_address),
error::permission_denied(EPOOL_NOT_IN_VALIDATOR_SET)
);
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)
);
assert!(
stake::is_current_epoch_validator(pool_address),
error::permission_denied(EPOOL_NOT_IN_VALIDATOR_SET)
);
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)
);
assert!(
stake::is_current_epoch_validator(pool_address),
error::permission_denied(EPOOL_NOT_IN_VALIDATOR_SET)
);
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 key
Variants
V1
Fields
execution_tiers: vector<transaction_limits::TxnLimitTier>
io_tiers: vector<transaction_limits::TxnLimitTier>

Enum RequestedMultipliers

Multipliers requested by the user.

INVARIANT: must match Rust enum for BCS serialization.

enum RequestedMultipliers has copy, drop, store
Variants
V1
Fields
execution_multiplier_percent: u64
Execution-gas multiplier (100 is 1x).
io_multiplier_percent: u64
IO-gas multiplier (100 is 1x).

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, drop
Variants
StakePoolOwner
Fields
multipliers: transaction_limits::RequestedMultipliers
DelegatedVoter
Fields
pool_address: address
multipliers: transaction_limits::RequestedMultipliers
DelegationPoolDelegator
Fields
pool_address: address
multipliers: transaction_limits::RequestedMultipliers