Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module 0x1::transaction_limits

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::aptos_governance;
use 0x1::delegation_pool;
use 0x1::error;
use 0x1::stake;
use 0x1::system_addresses;

Struct 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_bps: u64

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, 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, store
Variants
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, 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

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;

Function 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::TxnLimitTier
Implementation
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 }
}

Function 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;
    };
}

Function 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
}

Function 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): u64
Implementation
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
}

Function 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 }
    );
}

Function 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;
    }
}

Function 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)
    );
}

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