Module 0xb::limiter
- Struct
TransferLimiter
- Struct
TransferRecord
- Struct
UpdateRouteLimitEvent
- Constants
- Function
get_route_limit
- Function
new
- Function
check_and_record_sending_transfer
- Function
update_route_limit
- Function
current_hour_since_epoch
- Function
adjust_transfer_records
- Function
initial_transfer_limits
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
Fields
transfer_limits: vec_map::VecMap<chain_ids::BridgeRoute, u64>
transfer_records: vec_map::VecMap<chain_ids::BridgeRoute, limiter::TransferRecord>
Struct TransferRecord
struct TransferRecord has store
Struct UpdateRouteLimitEvent
struct UpdateRouteLimitEvent has copy, drop
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
Implementation
public fun get_route_limit(self: &TransferLimiter, route: &BridgeRoute): u64 {
self.transfer_limits[route]
}
Function new
public(friend) fun new(): limiter::TransferLimiter
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
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)
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
Implementation
fun current_hour_since_epoch(clock: &Clock): u64 {
clock::timestamp_ms(clock) / 3600000
}
Function adjust_transfer_records
fun adjust_transfer_records(self: &mut limiter::TransferRecord, current_hour_since_epoch: u64)
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>
Implementation
fun initial_transfer_limits(): VecMap<BridgeRoute, u64> {
let mut transfer_limits = vec_map::empty();
// 5M limit on Iota -> Ethereum mainnet
transfer_limits.insert(
chain_ids::get_route(chain_ids::eth_mainnet(), chain_ids::iota_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::iota_testnet()),
MAX_TRANSFER_LIMIT
);
transfer_limits.insert(
chain_ids::get_route(chain_ids::eth_sepolia(), chain_ids::iota_custom()),
MAX_TRANSFER_LIMIT
);
transfer_limits.insert(
chain_ids::get_route(chain_ids::eth_custom(), chain_ids::iota_testnet()),
MAX_TRANSFER_LIMIT
);
transfer_limits.insert(
chain_ids::get_route(chain_ids::eth_custom(), chain_ids::iota_custom()),
MAX_TRANSFER_LIMIT
);
transfer_limits
}