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 std::address;
use std::ascii;
use std::bcs;
use std::option;
use std::string;
use std::type_name;
use std::u64;
use std::vector;
use sui::accumulator;
use sui::address;
use sui::bag;
use sui::balance;
use sui::bcs;
use sui::clock;
use sui::coin;
use sui::config;
use sui::deny_list;
use sui::dynamic_field;
use sui::dynamic_object_field;
use sui::ecdsa_k1;
use sui::event;
use sui::hash;
use sui::hex;
use sui::linked_table;
use sui::object;
use sui::object_bag;
use sui::package;
use sui::party;
use sui::priority_queue;
use sui::sui;
use sui::table;
use sui::table_vec;
use sui::transfer;
use sui::tx_context;
use sui::types;
use sui::url;
use sui::vec_map;
use sui::vec_set;
use sui::versioned;
use sui_system::stake_subsidy;
use sui_system::staking_pool;
use sui_system::storage_fund;
use sui_system::sui_system;
use sui_system::sui_system_state_inner;
use sui_system::validator;
use sui_system::validator_cap;
use sui_system::validator_set;
use sui_system::validator_wrapper;
use sui_system::voting_power;

Struct `Bridge`

public struct Bridge has key
Fields
id: sui::object::UID
inner: sui::versioned::Versioned

Struct `BridgeInner`

public struct BridgeInner has store
Fields
bridge_version: u64
message_version: u8
chain_id: u8
sequence_nums: sui::vec_map::VecMap<u8, u64>
committee: bridge::committee::BridgeCommittee
treasury: bridge::treasury::BridgeTreasury
token_transfer_records: sui::linked_table::LinkedTable<bridge::message::BridgeMessageKey, bridge::bridge::BridgeRecord>
limiter: bridge::limiter::TransferLimiter
paused: bool

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
message_key: bridge::message::BridgeMessageKey

Struct `TokenTransferClaimed`

public struct TokenTransferClaimed has copy, drop
Fields
message_key: bridge::message::BridgeMessageKey

Struct `TokenTransferAlreadyApproved`

public struct TokenTransferAlreadyApproved has copy, drop
Fields
message_key: bridge::message::BridgeMessageKey

Struct `TokenTransferAlreadyClaimed`

public struct TokenTransferAlreadyClaimed has copy, drop
Fields
message_key: bridge::message::BridgeMessageKey

Struct `TokenTransferLimitExceed`

public struct TokenTransferLimitExceed has copy, drop
Fields
message_key: bridge::message::BridgeMessageKey

Constants

const MESSAGE_VERSION: u8 = 1;

const TRANSFER_STATUS_PENDING: u8 = 0;

const TRANSFER_STATUS_APPROVED: u8 = 1;

const TRANSFER_STATUS_CLAIMED: u8 = 2;

const TRANSFER_STATUS_NOT_FOUND: u8 = 3;

const EVM_ADDRESS_LENGTH: u64 = 20;

const EUnexpectedMessageType: u64 = 0;

const EUnauthorisedClaim: u64 = 1;

const EMalformedMessageError: u64 = 2;

const EUnexpectedTokenType: u64 = 3;

const EUnexpectedChainID: u64 = 4;

const ENotSystemAddress: u64 = 5;

const EUnexpectedSeqNum: u64 = 6;

const EWrongInnerVersion: u64 = 7;

const EBridgeUnavailable: u64 = 8;

const EUnexpectedOperation: u64 = 9;

const EInvariantSuiInitializedTokenTransferShouldNotBeClaimed: u64 = 10;

const EMessageNotFoundInRecords: u64 = 11;

const EUnexpectedMessageVersion: u64 = 12;

const EBridgeAlreadyPaused: u64 = 13;

const EBridgeNotPaused: u64 = 14;

const ETokenAlreadyClaimedOrHitLimit: u64 = 15;

const EInvalidBridgeRoute: u64 = 16;

const EMustBeTokenMessage: u64 = 17;

const EInvalidEvmAddress: u64 = 18;

const ETokenValueIsZero: u64 = 19;

const CURRENT_VERSION: u64 = 1;

Function `create`

fun create(id: sui::object::UID, chain_id: u8, ctx: &mut sui::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: sui::vec_map::VecMap<address, u64>, min_stake_participation_percentage: u64, ctx: &sui::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 sui_system::sui_system::SuiSystemState, bridge_pubkey_bytes: vector<u8>, http_rest_url: vector<u8>, ctx: &sui::tx_context::TxContext)
Implementation
public fun committee_registration(
    bridge: &mut Bridge,
    system_state: &mut SuiSystemState,
    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: &sui::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: sui::coin::TreasuryCap<T>, uc: sui::package::UpgradeCap, metadata: &sui::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: sui::coin::Coin<T>, ctx: &mut sui::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
    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 Sui, 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, EInvariantSuiInitializedTokenTransferShouldNotBeClaimed);
        // If record already has verified signatures, it means the message has been approved
        // Then we exit early.
        if (record.verified_signatures.is_some()) {
            event::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)) {
            event::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,
                },
            );
    };
    event::emit(TokenTransferApproved { message_key });
}

Function `claim_token`

public fun claim_token<T>(bridge: &mut bridge::bridge::Bridge, clock: &sui::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut sui::tx_context::TxContext): sui::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(), ETokenAlreadyClaimedOrHitLimit);
    maybe_token.destroy_some()
}

Function `claim_and_transfer_token`

public fun claim_and_transfer_token<T>(bridge: &mut bridge::bridge::Bridge, clock: &sui::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut sui::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_sui()) {
        let payload = message.extract_add_tokens_on_sui();
        inner.execute_add_tokens_on_sui(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: &sui::clock::Clock, source_chain: u8, bridge_seq_num: u64, ctx: &mut sui::tx_context::TxContext): (std::option::Option<sui::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) {
        event::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,
            )
    ) {
        event::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;
    event::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;
        event::emit(EmergencyOpEvent { frozen: true });
    } else if (op == message::emergency_op_unpause()) {
        assert!(inner.paused, EBridgeNotPaused);
        inner.paused = false;
        event::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_sui`

fun execute_add_tokens_on_sui(inner: &mut bridge::bridge::BridgeInner, payload: bridge::message::AddTokenOnSui)
Implementation
fun execute_add_tokens_on_sui(inner: &mut BridgeInner, payload: AddTokenOnSui) {
    // 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))
}