Skip to main content

Module 0xb::limiter

use 0x1::option;
use 0x1::vector;
use 0x2::clock;
use 0x2::event;
use 0x2::vec_map;
use 0xb::chain_ids;
use 0xb::treasury;

Struct TransferLimiter

struct TransferLimiter has store

Struct TransferRecord

struct TransferRecord has store
Click to open
Fields
hour_head: u64
hour_tail: u64
per_hour_amounts: vector<u64>
total_amount: u64

Struct UpdateRouteLimitEvent

struct UpdateRouteLimitEvent has copy, drop
Click to open
Fields
sending_chain: u8
receiving_chain: u8
new_limit: u64

Constants

const ELimitNotFoundForRoute: u64 = 0;

const MAX_TRANSFER_LIMIT: u64 = 18446744073709551615;

const USD_VALUE_MULTIPLIER: u64 = 100000000;

Function get_route_limit

public fun get_route_limit(self: &limiter::TransferLimiter, route: &chain_ids::BridgeRoute): u64
Click to open
Implementation
public fun get_route_limit(self: &TransferLimiter, route: &BridgeRoute): u64 {
    self.transfer_limits[route]
}

Function new

public(friend) fun new(): limiter::TransferLimiter
Click to open
Implementation
public(package) fun new(): TransferLimiter {
    // hardcoded limit for bridge genesis
    TransferLimiter {
        transfer_limits: initial_transfer_limits(),
        transfer_records: vec_map::empty()
    }
}

Function check_and_record_sending_transfer

public(friend) fun check_and_record_sending_transfer<T>(self: &mut limiter::TransferLimiter, treasury: &treasury::BridgeTreasury, clock: &clock::Clock, route: chain_ids::BridgeRoute, amount: u64): bool
Click to open
Implementation
public(package) fun check_and_record_sending_transfer<T>(
    self: &mut TransferLimiter,
    treasury: &BridgeTreasury,
    clock: &Clock,
    route: BridgeRoute,
    amount: u64
): bool {
    // Create record for route if not exists
    if (!self.transfer_records.contains(&route)) {
        self.transfer_records.insert(route, TransferRecord {
            hour_head: 0,
            hour_tail: 0,
            per_hour_amounts: vector[],
            total_amount: 0
        })
    };
    let record = self.transfer_records.get_mut(&route);
    let current_hour_since_epoch = current_hour_since_epoch(clock);

    record.adjust_transfer_records(current_hour_since_epoch);

    // Get limit for the route
    let route_limit = self.transfer_limits.try_get(&route);
    assert!(route_limit.is_some(), ELimitNotFoundForRoute);
    let route_limit = route_limit.destroy_some();
    let route_limit_adjusted =
        (route_limit as u128) * (treasury.decimal_multiplier<T>() as u128);

    // Compute notional amount
    // Upcast to u128 to prevent overflow, to not miss out on small amounts.
    let value = (treasury.notional_value<T>() as u128);
    let notional_amount_with_token_multiplier = value * (amount as u128);

    // Check if transfer amount exceed limit
    // Upscale them to the token's decimal.
    if ((record.total_amount as u128)
        * (treasury.decimal_multiplier<T>() as u128)
        + notional_amount_with_token_multiplier > route_limit_adjusted
    ) {
        return false
    };

    // Now scale down to notional value
    let notional_amount = notional_amount_with_token_multiplier
        / (treasury.decimal_multiplier<T>() as u128);
    // Should be safe to downcast to u64 after dividing by the decimals
    let notional_amount = (notional_amount as u64);

    // Record transfer value
    let new_amount = record.per_hour_amounts.pop_back() + notional_amount;
    record.per_hour_amounts.push_back(new_amount);
    record.total_amount = record.total_amount + notional_amount;
    true
}

Function update_route_limit

public(friend) fun update_route_limit(self: &mut limiter::TransferLimiter, route: &chain_ids::BridgeRoute, new_usd_limit: u64)
Click to open
Implementation
public(package) fun update_route_limit(
    self: &mut TransferLimiter,
    route: &BridgeRoute,
    new_usd_limit: u64
) {
    let receiving_chain = *route.destination();

    if (!self.transfer_limits.contains(route)) {
        self.transfer_limits.insert(*route, new_usd_limit);
    } else {
        *&mut self.transfer_limits[route] = new_usd_limit;
    };

    emit(UpdateRouteLimitEvent {
        sending_chain: *route.source(),
        receiving_chain,
        new_limit: new_usd_limit,
    })
}

Function current_hour_since_epoch

fun current_hour_since_epoch(clock: &clock::Clock): u64
Click to open
Implementation

Function adjust_transfer_records

fun adjust_transfer_records(self: &mut limiter::TransferRecord, current_hour_since_epoch: u64)
Click to open
Implementation
fun adjust_transfer_records(self: &mut TransferRecord, current_hour_since_epoch: u64) {
    if (self.hour_head == current_hour_since_epoch) {
        return // nothing to backfill
    };

    let target_tail = current_hour_since_epoch - 23;

    // If `hour_head` is even older than 24 hours ago, it means all items in
    // `per_hour_amounts` are to be evicted.
    if (self.hour_head < target_tail) {
        self.per_hour_amounts = vector[];
        self.total_amount = 0;
        self.hour_tail = target_tail;
        self.hour_head = target_tail;
        // Don't forget to insert this hour's record
        self.per_hour_amounts.push_back(0);
    } else {
        // self.hour_head is within 24 hour range.
        // some items in `per_hour_amounts` are still valid, we remove stale hours.
        while (self.hour_tail < target_tail) {
            self.total_amount = self.total_amount - self.per_hour_amounts.remove(0);
            self.hour_tail = self.hour_tail + 1;
        }
    };

    // Backfill from hour_head to current hour
    while (self.hour_head < current_hour_since_epoch) {
        self.per_hour_amounts.push_back(0);
        self.hour_head = self.hour_head + 1;
    }
}

Function initial_transfer_limits

fun initial_transfer_limits(): vec_map::VecMap<chain_ids::BridgeRoute, u64>
Click to open
Implementation
fun initial_transfer_limits(): VecMap<BridgeRoute, u64> {
    let mut transfer_limits = vec_map::empty();
    // 5M limit on Sui -> Ethereum mainnet
    transfer_limits.insert(
        chain_ids::get_route(chain_ids::eth_mainnet(), chain_ids::sui_mainnet()),
        5_000_000 * USD_VALUE_MULTIPLIER
    );

    // MAX limit for testnet and devnet
    transfer_limits.insert(
        chain_ids::get_route(chain_ids::eth_sepolia(), chain_ids::sui_testnet()),
        MAX_TRANSFER_LIMIT
    );

    transfer_limits.insert(
        chain_ids::get_route(chain_ids::eth_sepolia(), chain_ids::sui_custom()),
        MAX_TRANSFER_LIMIT
    );

    transfer_limits.insert(
        chain_ids::get_route(chain_ids::eth_custom(), chain_ids::sui_testnet()),
        MAX_TRANSFER_LIMIT
    );

    transfer_limits.insert(
        chain_ids::get_route(chain_ids::eth_custom(), chain_ids::sui_custom()),
        MAX_TRANSFER_LIMIT
    );

    transfer_limits
}