Skip to main content

Module bridge::bridge

use bridge::chain_ids; use bridge::committee; use bridge::crypto; use bridge::limiter; use bridge::message; use bridge::message_types; use bridge::treasury; use iota::address; use iota::bag; use iota::balance; use iota::bcs; use iota::clock; use iota::coin; use iota::config; use iota::deny_list; use iota::dynamic_field; use iota::dynamic_object_field; use iota::ecdsa_k1; use iota::event; use iota::hash; use iota::hex; use iota::iota; use iota::linked_table; use iota::object; use iota::object_bag; use iota::package; use iota::pay; use iota::priority_queue; use iota::system_admin_cap; use iota::table; use iota::table_vec; use iota::transfer; use iota::tx_context; use iota::types; use iota::url; use iota::vec_map; use iota::vec_set; use iota::versioned; use iota_system::iota_system; use iota_system::iota_system_state_inner; use iota_system::staking_pool; use iota_system::storage_fund; use iota_system::validator; use iota_system::validator_cap; use iota_system::validator_set; use iota_system::validator_wrapper; use iota_system::voting_power; use std::address; use std::ascii; use std::bcs; use std::option; use std::string; use std::type_name; use std::u64; use std::vector;

Struct Bridge

public struct Bridge has key

Fields

Struct BridgeInner

public struct BridgeInner has store

Fields

Struct TokenDepositedEvent

public struct TokenDepositedEvent has copy, drop

Fields
seq_num: u64
source_chain: u8
sender_address: vector<u8>
target_chain: u8
target_address: vector<u8>
token_type: u8
amount: u64

Struct EmergencyOpEvent

public struct EmergencyOpEvent has copy, drop

Fields
frozen: bool

Struct BridgeRecord

public struct BridgeRecord has drop, store

Fields
message: bridge::message::BridgeMessage
verified_signatures: std::option::Option<vector<vector<u8>>>
claimed: bool

Struct TokenTransferApproved

public struct TokenTransferApproved has copy, drop

Fields

Struct TokenTransferClaimed

public struct TokenTransferClaimed has copy, drop

Fields

Struct TokenTransferAlreadyApproved

public struct TokenTransferAlreadyApproved has copy, drop

Fields

Struct TokenTransferAlreadyClaimed

public struct TokenTransferAlreadyClaimed has copy, drop

Fields

Struct TokenTransferLimitExceed

public struct TokenTransferLimitExceed has copy, drop

Fields

Constants

const CURRENT_VERSION: u64 = 1;

const EBridgeAlreadyPaused: u64 = 13;

const EBridgeNotPaused: u64 = 14;

const EBridgeUnavailable: u64 = 8;

const EInvalidBridgeRoute: u64 = 16;

const EInvalidEvmAddress: u64 = 18;

const EInvariantIotaInitializedTokenTransferShouldNotBeClaimed: u64 = 10;

const EMalformedMessageError: u64 = 2;

const EMessageNotFoundInRecords: u64 = 11;

const EMustBeTokenMessage: u64 = 17;

const ENotSystemAddress: u64 = 5;

const ETokenAlreadyClaimed: u64 = 15;

const ETokenValueIsZero: u64 = 19;

const EUnauthorisedClaim: u64 = 1;

const EUnexpectedChainID: u64 = 4;

const EUnexpectedMessageType: u64 = 0;

const EUnexpectedMessageVersion: u64 = 12;

const EUnexpectedOperation: u64 = 9;

const EUnexpectedSeqNum: u64 = 6;

const EUnexpectedTokenType: u64 = 3;

const EVM_ADDRESS_LENGTH: u64 = 20;

const EWrongInnerVersion: u64 = 7;

const MESSAGE_VERSION: u8 = 1;

const TRANSFER_STATUS_APPROVED: u8 = 1;

const TRANSFER_STATUS_CLAIMED: u8 = 2;

const TRANSFER_STATUS_NOT_FOUND: u8 = 3;

const TRANSFER_STATUS_PENDING: u8 = 0;

Function create

fun create(id: iota::object::UID, chain_id: u8, ctx: &mut iota::tx_context::TxContext)

Implementation

fun create(id: UID, chain_id: u8, ctx: &mut TxContext) { assert!(ctx.sender() == @0x0, ENotSystemAddress); let bridge_inner = BridgeInner { bridge_version: CURRENT_VERSION, message_version: MESSAGE_VERSION, chain_id, sequence_nums: vec_map::empty(), committee: committee::create(ctx), treasury: treasury::create(ctx), token_transfer_records: linked_table::new(ctx), limiter: limiter::new(), paused: false, }; let bridge = Bridge { id, inner: versioned::create(CURRENT_VERSION, bridge_inner, ctx), }; transfer::share_object(bridge); }

Function init_bridge_committee

fun init_bridge_committee(bridge: &mut bridge::bridge::Bridge, active_validator_voting_power: iota::vec_map::VecMap<address, u64>, min_stake_participation_percentage: u64, ctx: &iota::tx_context::TxContext)

Implementation

fun init_bridge_committee( bridge: &mut Bridge, active_validator_voting_power: VecMap<address, u64>, min_stake_participation_percentage: u64, ctx: &TxContext, ) { assert!(ctx.sender() == @0x0, ENotSystemAddress); let inner = load_inner_mut(bridge); if (inner.committee.committee_members().is_empty()) { inner .committee .try_create_next_committee( active_validator_voting_power, min_stake_participation_percentage, ctx, ) } }

Function committee_registration

public fun committee_registration(bridge: &mut bridge::bridge::Bridge, system_state: &mut iota_system::iota_system::IotaSystemState, bridge_pubkey_bytes: vector<u8>, http_rest_url: vector<u8>, ctx: &iota::tx_context::TxContext)

Implementation

public fun committee_registration( bridge: &mut Bridge, system_state: &mut IotaSystemState, bridge_pubkey_bytes: vector<u8>, http_rest_url: vector<u8>, ctx: &TxContext, ) { load_inner_mut(bridge) .committee .register(system_state, bridge_pubkey_bytes, http_rest_url, ctx); }

Function update_node_url

public fun update_node_url(bridge: &mut bridge::bridge::Bridge, new_url: vector<u8>, ctx: &iota::tx_context::TxContext)

Implementation

public fun update_node_url(bridge: &mut Bridge, new_url: vector<u8>, ctx: &TxContext) { load_inner_mut(bridge).committee.update_node_url(new_url, ctx); }

Function register_foreign_token

public fun register_foreign_token<T>(bridge: &mut bridge::bridge::Bridge, tc: iota::coin::TreasuryCap<T>, uc: iota:📦:UpgradeCap, metadata: &iota::coin::CoinMetadata<T>)

Implementation

public fun register_foreign_token<T>( bridge: &mut Bridge, tc: TreasuryCap<T>, uc: UpgradeCap, metadata: &CoinMetadata<T>, ) { load_inner_mut(bridge).treasury.register_foreign_token<T>(tc, uc, metadata) }

Function send_token

public fun send_token<T>(bridge: &mut bridge::bridge::Bridge, target_chain: u8, target_address: vector<u8>, token: iota::coin::Coin<T>, ctx: &mut iota::tx_context::TxContext)

Implementation

public fun send_token<T>( bridge: &mut Bridge, target_chain: u8, target_address: vector<u8>, token: Coin<T>, ctx: &mut TxContext, ) { let inner = load_inner_mut(bridge); assert!(!inner.paused, EBridgeUnavailable); assert!(chain_ids::is_valid_route(inner.chain_id, target_chain), EInvalidBridgeRoute); assert!(target_address.length() == EVM_ADDRESS_LENGTH, EInvalidEvmAddress); let bridge_seq_num = inner.get_current_seq_num_and_increment(message_types::token()); let token_id = inner.treasury.token_id<T>(); let token_amount = token.balance().value(); assert!(token_amount > 0, ETokenValueIsZero); // create bridge message let message = message::create_token_bridge_message( inner.chain_id, bridge_seq_num, address::to_bytes(ctx.sender()), target_chain, target_address, token_id, token_amount, ); // burn / escrow token, unsupported coins will fail in this step inner.treasury.burn(token); // Store pending bridge request inner .token_transfer_records .push_back( message.key(), BridgeRecord { message, verified_signatures: option::none(), claimed: false, }, ); // emit event emit(TokenDepositedEvent { seq_num: bridge_seq_num, source_chain: inner.chain_id, sender_address: address::to_bytes(ctx.sender()), target_chain, target_address, token_type: token_id, amount: token_amount, }); }

Function approve_token_transfer

public fun approve_token_transfer(bridge: &mut bridge::bridge::Bridge, message: bridge::message::BridgeMessage, signatures: vector<vector<u8>>)

Implementation

public fun approve_token_transfer( bridge: &mut Bridge, message: BridgeMessage, signatures: vector<vector<u8>>, ) { let inner = load_inner_mut(bridge); assert!(!inner.paused, EBridgeUnavailable); // verify signatures inner.committee.verify_signatures(message, signatures); assert!(message.message_type() == message_types::token(), EMustBeTokenMessage); assert!(message.message_version() == MESSAGE_VERSION, EUnexpectedMessageVersion); let token_payload = message.extract_token_bridge_payload(); let target_chain = token_payload.token_target_chain(); assert!( message.source_chain() == inner.chain_id || target_chain == inner.chain_id, EUnexpectedChainID, ); let message_key = message.key(); // retrieve pending message if source chain is IOTA, the initial message // must exist on chain if (message.source_chain() == inner.chain_id) { let record = &mut inner.token_transfer_records[message_key]; assert!(record.message == message, EMalformedMessageError); assert!(!record.claimed, EInvariantIotaInitializedTokenTransferShouldNotBeClaimed); // If record already has verified signatures, it means the message has been approved // Then we exit early. if (record.verified_signatures.is_some()) { emit(TokenTransferAlreadyApproved { message_key }); return }; // Store approval record.verified_signatures = option::some(signatures) } else { // At this point, if this message is in token_transfer_records, we know // it's already approved because we only add a message to token_transfer_records // after verifying the signatures if (inner.token_transfer_records.contains(message_key)) { emit(TokenTransferAlreadyApproved { message_key }); return }; // Store message and approval inner .token_transfer_records .push_back( message_key, BridgeRecord { message, verified_signatures: option::some(signatures), claimed: false, }, ); }; emit(TokenTransferApproved { message_key }); }

Function claim_token

public fun claim_token<T>(bridge: &mut bridge::bridge::Bridge, clock: &iota::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut iota::tx_context::TxContext): iota::coin::Coin<T>

Implementation

public fun claim_token<T>( bridge: &mut Bridge, clock: &Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut TxContext, ): Coin<T> { let (maybe_token, owner) = bridge.claim_token_internal<T>( clock, source_chain, bridge_seq_num, ctx, ); // Only token owner can claim the token assert!(ctx.sender() == owner, EUnauthorisedClaim); assert!(maybe_token.is_some(), ETokenAlreadyClaimed); maybe_token.destroy_some() }

Function claim_and_transfer_token

public fun claim_and_transfer_token<T>(bridge: &mut bridge::bridge::Bridge, clock: &iota::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut iota::tx_context::TxContext)

Implementation

public fun claim_and_transfer_token<T>( bridge: &mut Bridge, clock: &Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut TxContext, ) { let (token, owner) = bridge.claim_token_internal<T>(clock, source_chain, bridge_seq_num, ctx); if (token.is_some()) { transfer::public_transfer(token.destroy_some(), owner) } else { token.destroy_none(); }; }

Function execute_system_message

public fun execute_system_message(bridge: &mut bridge::bridge::Bridge, message: bridge::message::BridgeMessage, signatures: vector<vector<u8>>)

Implementation

public fun execute_system_message( bridge: &mut Bridge, message: BridgeMessage, signatures: vector<vector<u8>>, ) { let message_type = message.message_type(); // TODO: test version mismatch assert!(message.message_version() == MESSAGE_VERSION, EUnexpectedMessageVersion); let inner = load_inner_mut(bridge); assert!(message.source_chain() == inner.chain_id, EUnexpectedChainID); // check system ops seq number and increment it let expected_seq_num = inner.get_current_seq_num_and_increment(message_type); assert!(message.seq_num() == expected_seq_num, EUnexpectedSeqNum); inner.committee.verify_signatures(message, signatures); if (message_type == message_types::emergency_op()) { let payload = message.extract_emergency_op_payload(); inner.execute_emergency_op(payload); } else if (message_type == message_types::committee_blocklist()) { let payload = message.extract_blocklist_payload(); inner.committee.execute_blocklist(payload); } else if (message_type == message_types::update_bridge_limit()) { let payload = message.extract_update_bridge_limit(); inner.execute_update_bridge_limit(payload); } else if (message_type == message_types::update_asset_price()) { let payload = message.extract_update_asset_price(); inner.execute_update_asset_price(payload); } else if (message_type == message_types::add_tokens_on_iota()) { let payload = message.extract_add_tokens_on_iota(); inner.execute_add_tokens_on_iota(payload); } else { abort EUnexpectedMessageType }; }

Function get_token_transfer_action_status

fun get_token_transfer_action_status(bridge: &bridge::bridge::Bridge, source_chain: u8, bridge_seq_num: u64): u8

Implementation

fun get_token_transfer_action_status(bridge: &Bridge, source_chain: u8, bridge_seq_num: u64): u8 { let inner = load_inner(bridge); let key = message::create_key( source_chain, message_types::token(), bridge_seq_num, ); if (!inner.token_transfer_records.contains(key)) { return TRANSFER_STATUS_NOT_FOUND }; let record = &inner.token_transfer_records[key]; if (record.claimed) { return TRANSFER_STATUS_CLAIMED }; if (record.verified_signatures.is_some()) { return TRANSFER_STATUS_APPROVED }; TRANSFER_STATUS_PENDING }

Function get_token_transfer_action_signatures

fun get_token_transfer_action_signatures(bridge: &bridge::bridge::Bridge, source_chain: u8, bridge_seq_num: u64): std::option::Option<vector<vector<u8>>>

Implementation

fun get_token_transfer_action_signatures( bridge: &Bridge, source_chain: u8, bridge_seq_num: u64, ): Option<vector<vector<u8>>> { let inner = load_inner(bridge); let key = message::create_key( source_chain, message_types::token(), bridge_seq_num, ); if (!inner.token_transfer_records.contains(key)) { return option::none() }; let record = &inner.token_transfer_records[key]; record.verified_signatures }

Function load_inner

fun load_inner(bridge: &bridge::bridge::Bridge): &bridge::bridge::BridgeInner

Implementation

fun load_inner(bridge: &Bridge): &BridgeInner { let version = bridge.inner.version(); // TODO: Replace this with a lazy update function when we add a new version of the inner object. assert!(version == CURRENT_VERSION, EWrongInnerVersion); let inner: &BridgeInner = bridge.inner.load_value(); assert!(inner.bridge_version == version, EWrongInnerVersion); inner }

Function load_inner_mut

fun load_inner_mut(bridge: &mut bridge::bridge::Bridge): &mut bridge::bridge::BridgeInner

Implementation

fun load_inner_mut(bridge: &mut Bridge): &mut BridgeInner { let version = bridge.inner.version(); // TODO: Replace this with a lazy update function when we add a new version of the inner object. assert!(version == CURRENT_VERSION, EWrongInnerVersion); let inner: &mut BridgeInner = bridge.inner.load_value_mut(); assert!(inner.bridge_version == version, EWrongInnerVersion); inner }

Function claim_token_internal

fun claim_token_internal<T>(bridge: &mut bridge::bridge::Bridge, clock: &iota::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut iota::tx_context::TxContext): (std::option::Option<iota::coin::Coin<T>>, address)

Implementation

fun claim_token_internal<T>( bridge: &mut Bridge, clock: &Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut TxContext, ): (Option<Coin<T>>, address) { let inner = load_inner_mut(bridge); assert!(!inner.paused, EBridgeUnavailable); let key = message::create_key(source_chain, message_types::token(), bridge_seq_num); assert!(inner.token_transfer_records.contains(key), EMessageNotFoundInRecords); // retrieve approved bridge message let record = &mut inner.token_transfer_records[key]; // ensure this is a token bridge message assert!(&record.message.message_type() == message_types::token(), EUnexpectedMessageType); // Ensure it's signed assert!(record.verified_signatures.is_some(), EUnauthorisedClaim); // extract token message let token_payload = record.message.extract_token_bridge_payload(); // get owner address let owner = address::from_bytes(token_payload.token_target_address()); // If already claimed, exit early if (record.claimed) { emit(TokenTransferAlreadyClaimed { message_key: key }); return (option::none(), owner) }; let target_chain = token_payload.token_target_chain(); // ensure target chain matches bridge.chain_id assert!(target_chain == inner.chain_id, EUnexpectedChainID); // TODO: why do we check validity of the route here? what if inconsistency? // Ensure route is valid // TODO: add unit tests // get_route abort if route is invalid let route = chain_ids::get_route(source_chain, target_chain); // check token type assert!( treasury::token_id<T>(&inner.treasury) == token_payload.token_type(), EUnexpectedTokenType, ); let amount = token_payload.token_amount(); // Make sure transfer is within limit. if ( !inner .limiter .check_and_record_sending_transfer<T>( &inner.treasury, clock, route, amount, ) ) { emit(TokenTransferLimitExceed { message_key: key }); return (option::none(), owner) }; // claim from treasury let token = inner.treasury.mint<T>(amount, ctx); // Record changes record.claimed = true; emit(TokenTransferClaimed { message_key: key }); (option::some(token), owner) }

Function execute_emergency_op

fun execute_emergency_op(inner: &mut bridge::bridge::BridgeInner, payload: bridge::message::EmergencyOp)

Implementation

fun execute_emergency_op(inner: &mut BridgeInner, payload: EmergencyOp) { let op = payload.emergency_op_type(); if (op == message::emergency_op_pause()) { assert!(!inner.paused, EBridgeAlreadyPaused); inner.paused = true; emit(EmergencyOpEvent { frozen: true }); } else if (op == message::emergency_op_unpause()) { assert!(inner.paused, EBridgeNotPaused); inner.paused = false; emit(EmergencyOpEvent { frozen: false }); } else { abort EUnexpectedOperation }; }

Function execute_update_bridge_limit

fun execute_update_bridge_limit(inner: &mut bridge::bridge::BridgeInner, payload: bridge::message::UpdateBridgeLimit)

Implementation

fun execute_update_bridge_limit(inner: &mut BridgeInner, payload: UpdateBridgeLimit) { let receiving_chain = payload.update_bridge_limit_payload_receiving_chain(); assert!(receiving_chain == inner.chain_id, EUnexpectedChainID); let route = chain_ids::get_route( payload.update_bridge_limit_payload_sending_chain(), receiving_chain, ); inner .limiter .update_route_limit( &route, payload.update_bridge_limit_payload_limit(), ) }

Function execute_update_asset_price

fun execute_update_asset_price(inner: &mut bridge::bridge::BridgeInner, payload: bridge::message::UpdateAssetPrice)

Implementation

fun execute_update_asset_price(inner: &mut BridgeInner, payload: UpdateAssetPrice) { inner .treasury .update_asset_notional_price( payload.update_asset_price_payload_token_id(), payload.update_asset_price_payload_new_price(), ) }

Function execute_add_tokens_on_iota

fun execute_add_tokens_on_iota(inner: &mut bridge::bridge::BridgeInner, payload: bridge::message::AddTokenOnIota)

Implementation

fun execute_add_tokens_on_iota(inner: &mut BridgeInner, payload: AddTokenOnIota) { // FIXME: assert native_token to be false and add test let native_token = payload.is_native(); let mut token_ids = payload.token_ids(); let mut token_type_names = payload.token_type_names(); let mut token_prices = payload.token_prices(); // Make sure token data is consistent assert!(token_ids.length() == token_type_names.length(), EMalformedMessageError); assert!(token_ids.length() == token_prices.length(), EMalformedMessageError); while (token_ids.length() > 0) { let token_id = token_ids.pop_back(); let token_type_name = token_type_names.pop_back(); let token_price = token_prices.pop_back(); inner.treasury.add_new_token(token_type_name, token_id, native_token, token_price) } }

Function get_current_seq_num_and_increment

fun get_current_seq_num_and_increment(bridge: &mut bridge::bridge::BridgeInner, msg_type: u8): u64

Implementation

fun get_current_seq_num_and_increment(bridge: &mut BridgeInner, msg_type: u8): u64 { if (!bridge.sequence_nums.contains(&msg_type)) { bridge.sequence_nums.insert(msg_type, 1); return 0 }; let entry = &mut bridge.sequence_nums[&msg_type]; let seq_num = *entry; *entry = seq_num + 1; seq_num }

Function get_parsed_token_transfer_message

fun get_parsed_token_transfer_message(bridge: &bridge::bridge::Bridge, source_chain: u8, bridge_seq_num: u64): std::option::Option<bridge::message::ParsedTokenTransferMessage>

Implementation

fun get_parsed_token_transfer_message( bridge: &Bridge, source_chain: u8, bridge_seq_num: u64, ): Option<ParsedTokenTransferMessage> { let inner = load_inner(bridge); let key = message::create_key( source_chain, message_types::token(), bridge_seq_num, ); if (!inner.token_transfer_records.contains(key)) { return option::none() }; let record = &inner.token_transfer_records[key]; let message = &record.message; option::some(to_parsed_token_transfer_message(message)) }