axelar-cgp-sui

Module 0xa1::gateway

Implementation of the Axelar Gateway for Sui Move.

This code is based on the following:

I. Sending call approvals

A approval is sent through the send function, a Channel is supplied to determine the source -> ID. Event is then emitted and Axelar network can operate

II. Receiving call approvals

Approval bytes and signatures are passed into create function to generate a CallApproval object.

Once created, CallApproval needs to be consumed. And the only way to do it is by calling consume_call_approval function and pass a correct Channel instance alongside the CallApproval.

The Gateway object uses a versioned field to support upgradability. The current implementation uses Gateway_v0.

use 0x1::ascii;
use 0x1::vector;
use 0x2::bcs;
use 0x2::clock;
use 0x2::object;
use 0x2::table;
use 0x2::transfer;
use 0x2::tx_context;
use 0x2::versioned;
use 0xa1::auth;
use 0xa1::bytes32;
use 0xa1::channel;
use 0xa1::gateway_v0;
use 0xa1::message_status;
use 0xa1::message_ticket;
use 0xa1::weighted_signers;
use 0xb0::version_control;

Resource Gateway

struct Gateway has key
Fields
id: object::UID
inner: versioned::Versioned

Resource CreatorCap

struct CreatorCap has store, key
Fields
id: object::UID

Constants

const VERSION: u64 = 0;

Function init

Init the module by giving a CreatorCap to the sender to allow a full setup.

fun init(ctx: &mut tx_context::TxContext)
Implementation
fun init(ctx: &mut TxContext) {
    let cap = CreatorCap {
        id: object::new(ctx),
    };

    transfer::transfer(cap, ctx.sender());
}

Function setup

Setup the module by creating a new Gateway object.

entry fun setup(cap: gateway::CreatorCap, operator: address, domain_separator: address, minimum_rotation_delay: u64, previous_signers_retention: u64, initial_signers: vector<u8>, clock: &clock::Clock, ctx: &mut tx_context::TxContext)
Implementation
entry fun setup(
    cap: CreatorCap,
    operator: address,
    domain_separator: address,
    minimum_rotation_delay: u64,
    previous_signers_retention: u64,
    initial_signers: vector<u8>,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    let CreatorCap { id } = cap;
    id.delete();

    let inner = versioned::create(
        VERSION,
        gateway_v0::new(
            operator,
            table::new(ctx),
            auth::setup(
                bytes32::new(domain_separator),
                minimum_rotation_delay,
                previous_signers_retention,
                utils::peel!(
                    initial_signers,
                    |bcs| weighted_signers::peel(bcs),
                ),
                clock,
                ctx,
            ),
            version_control(),
        ),
        ctx,
    );

    // Share the gateway object for anyone to use.
    transfer::share_object(Gateway {
        id: object::new(ctx),
        inner,
    });
}

Function approve_messages

The main entrypoint for approving Axelar signed messages. If proof is valid, message approvals are stored in the Gateway object, if not already approved before. This method is only intended to be called via a Transaction Block, keeping more flexibility for upgrades.

entry fun approve_messages(self: &mut gateway::Gateway, message_data: vector<u8>, proof_data: vector<u8>)
Implementation
entry fun approve_messages(
    self: &mut Gateway,
    message_data: vector<u8>,
    proof_data: vector<u8>,
) {
    let value = self.value_mut!(b"approve_messages");
    value.approve_messages(message_data, proof_data);
}

Function rotate_signers

The main entrypoint for rotating Axelar signers. If proof is valid, signers stored on the Gateway object are rotated. This method is only intended to be called via a Transaction Block, keeping more flexibility for upgrades.

entry fun rotate_signers(self: &mut gateway::Gateway, clock: &clock::Clock, new_signers_data: vector<u8>, proof_data: vector<u8>, ctx: &tx_context::TxContext)
Implementation
entry fun rotate_signers(
    self: &mut Gateway,
    clock: &Clock,
    new_signers_data: vector<u8>,
    proof_data: vector<u8>,
    ctx: &TxContext,
) {
    let value = self.value_mut!(b"rotate_signers");
    value.rotate_signers(
        clock,
        new_signers_data,
        proof_data,
        ctx,
    )
}

Function prepare_message

Prepare a MessageTicket to call a contract on the destination chain.

public fun prepare_message(channel: &channel::Channel, destination_chain: ascii::String, destination_address: ascii::String, payload: vector<u8>): message_ticket::MessageTicket
Implementation
public fun prepare_message(
    channel: &Channel,
    destination_chain: String,
    destination_address: String,
    payload: vector<u8>,
): MessageTicket {
    message_ticket::new(
        channel.to_address(),
        destination_chain,
        destination_address,
        payload,
        VERSION,
    )
}

Function send_message

Submit the MessageTicket which causes a contract call by sending an event from an authorized Channel.

public fun send_message(self: &gateway::Gateway, message: message_ticket::MessageTicket)
Implementation
public fun send_message(
    self: &Gateway,
    message: MessageTicket,
) {
    let value = self.value!(b"send_message");
    value.send_message(message, VERSION);
}

Function is_message_approved

public fun is_message_approved(self: &gateway::Gateway, source_chain: ascii::String, message_id: ascii::String, source_address: ascii::String, destination_id: address, payload_hash: bytes32::Bytes32): bool
Implementation
public fun is_message_approved(
    self: &Gateway,
    source_chain: String,
    message_id: String,
    source_address: String,
    destination_id: address,
    payload_hash: Bytes32,
): bool {
    let value = self.value!(b"is_message_approved");
    value.is_message_approved(
        source_chain,
        message_id,
        source_address,
        destination_id,
        payload_hash,
    )
}

Function is_message_executed

public fun is_message_executed(self: &gateway::Gateway, source_chain: ascii::String, message_id: ascii::String): bool
Implementation
public fun is_message_executed(
    self: &Gateway,
    source_chain: String,
    message_id: String,
): bool {
    let value = self.value!(b"is_message_executed");
    value.is_message_executed(source_chain, message_id)
}

Function take_approved_message

To execute a message, the relayer will call take_approved_message to get the hot potato ApprovedMessage object, and then trigger the app’s package via discovery.

public fun take_approved_message(self: &mut gateway::Gateway, source_chain: ascii::String, message_id: ascii::String, source_address: ascii::String, destination_id: address, payload: vector<u8>): channel::ApprovedMessage
Implementation
public fun take_approved_message(
    self: &mut Gateway,
    source_chain: String,
    message_id: String,
    source_address: String,
    destination_id: address,
    payload: vector<u8>,
): ApprovedMessage {
    let value = self.value_mut!(b"take_approved_message");
    value.take_approved_message(
        source_chain,
        message_id,
        source_address,
        destination_id,
        payload,
    )
}

Function version_control

fun version_control(): version_control::VersionControl
Implementation
fun version_control(): VersionControl {
    version_control::new(
        vector [
            // Version 0
            vector [
                b"approve_messages",
                b"rotate_signers",
                b"is_message_approved",
                b"is_message_executed",
                b"take_approved_message",
                b"send_message",
            ].map!(|function_name| function_name.to_ascii_string())
        ]
    )
}