From 1ae96366106bfa304b9300b906574530d88213fb Mon Sep 17 00:00:00 2001 From: npty Date: Fri, 23 Aug 2024 07:40:54 +0000 Subject: [PATCH] chore: format by Prettier Move plugin --- move/abi/sources/abi.move | 524 +++++------ move/axelar_gateway/sources/auth.move | 396 ++++---- move/axelar_gateway/sources/channel.move | 459 ++++----- move/axelar_gateway/sources/discovery.move | 541 +++++------ move/axelar_gateway/sources/gateway.move | 621 +++++++------ .../axelar_gateway/sources/types/bytes32.move | 92 +- .../axelar_gateway/sources/types/message.move | 126 +-- move/axelar_gateway/sources/types/proof.move | 132 +-- .../sources/types/weighted_signer.move | 140 +-- .../sources/types/weighted_signers.move | 97 +- move/example/sources/gmp/gmp.move | 166 ++-- move/gas_service/sources/gas_service.move | 693 +++++++------- .../sources/governance/governance.move | 877 ++++++++++-------- .../sources/interchain_token.move | 36 +- move/its/sources/address_tracker.move | 190 ++-- move/its/sources/coin_info.move | 132 +-- move/its/sources/coin_management.move | 390 ++++---- move/its/sources/discovery.move | 700 +++++++------- move/its/sources/flow_limit.move | 96 +- move/its/sources/its.move | 472 +++++----- move/its/sources/service.move | 585 ++++++------ move/its/sources/token_id.move | 181 ++-- move/its/sources/utils.move | 188 ++-- move/its/tests/coin_init_test.move | 46 +- move/operators/sources/operators.move | 682 +++++++------- move/squid/sources/squid/coin_bag.move | 157 ++-- move/squid/sources/squid/deepbook_v2.move | 781 +++++++++------- move/squid/sources/squid/discovery.move | 295 +++--- move/squid/sources/squid/squid.move | 87 +- move/squid/sources/squid/swap_info.move | 178 ++-- move/squid/sources/squid/transfers.move | 379 ++++---- 31 files changed, 5495 insertions(+), 4944 deletions(-) diff --git a/move/abi/sources/abi.move b/move/abi/sources/abi.move index 872e6d81..84e91b80 100644 --- a/move/abi/sources/abi.move +++ b/move/abi/sources/abi.move @@ -1,347 +1,353 @@ /// This module implements ABI encoding/decoding methods for interoperability with EVM message format. /// /// ABI Specification: https://docs.soliditylang.org/en/v0.8.26/abi-spec.html -module abi::abi { - // ----- - // Types - // ----- - - public struct AbiReader has copy, drop { - bytes: vector, - head: u64, - pos: u64, - } +module abi::abi; - public struct AbiWriter has copy, drop { - bytes: vector, - pos: u64, - } +// ----- +// Types +// ----- - // ---------------- - // Public Functions - // ---------------- +public struct AbiReader has copy, drop { + bytes: vector, + head: u64, + pos: u64, +} - public fun new_reader(bytes: vector): AbiReader { - AbiReader { - bytes, - head: 0, - pos: 0, - } +public struct AbiWriter has copy, drop { + bytes: vector, + pos: u64, +} + +// ---------------- +// Public Functions +// ---------------- + +public fun new_reader(bytes: vector): AbiReader { + AbiReader { + bytes, + head: 0, + pos: 0, } +} - public fun new_writer(length: u64): AbiWriter { - let mut bytes = vector[]; - let mut i = 0; +public fun new_writer(length: u64): AbiWriter { + let mut bytes = vector[]; + let mut i = 0; - while (i < 32 * length) { - bytes.push_back(0); - i = i + 1; - }; + while (i < 32 * length) { + bytes.push_back(0); + i = i + 1; + }; - AbiWriter { - bytes, - pos: 0, - } + AbiWriter { + bytes, + pos: 0, } +} - public fun into_bytes(self: AbiWriter): vector { - let AbiWriter {bytes, pos: _} = self; +public fun into_bytes(self: AbiWriter): vector { + let AbiWriter { bytes, pos: _ } = self; - bytes - } + bytes +} - // TODO: check that all bytes were decoded - public fun into_remaining_bytes(self: AbiReader): vector { - let AbiReader {bytes, head: _, pos: _} = self; +// TODO: check that all bytes were decoded +public fun into_remaining_bytes(self: AbiReader): vector { + let AbiReader { bytes, head: _, pos: _ } = self; - bytes - } + bytes +} - public fun read_u256(self: &mut AbiReader): u256 { - let mut var = 0u256; - let mut i = 0; - let pos = self.pos; +public fun read_u256(self: &mut AbiReader): u256 { + let mut var = 0u256; + let mut i = 0; + let pos = self.pos; - while (i < 32) { - var = (var << 8) | (self.bytes[i + pos] as u256); - i = i + 1; - }; + while (i < 32) { + var = (var << 8) | (self.bytes[i + pos] as u256); + i = i + 1; + }; - self.pos = pos + 32; + self.pos = pos + 32; - var - } + var +} - public fun read_u8(self: &mut AbiReader): u8 { - self.read_u256() as u8 - } +public fun read_u8(self: &mut AbiReader): u8 { + self.read_u256() as u8 +} - public fun skip_slot(self: &mut AbiReader) { - self.pos = self.pos + 32; - } +public fun skip_slot(self: &mut AbiReader) { + self.pos = self.pos + 32; +} - public fun read_bytes(self: &mut AbiReader): vector { - let pos = self.pos; +public fun read_bytes(self: &mut AbiReader): vector { + let pos = self.pos; - // Move position to the start of the bytes - let offset = self.read_u256() as u64; - self.pos = self.head + offset; + // Move position to the start of the bytes + let offset = self.read_u256() as u64; + self.pos = self.head + offset; - let var = self.decode_bytes(); + let var = self.decode_bytes(); - // Move position to the next slot - self.pos = pos + 32; + // Move position to the next slot + self.pos = pos + 32; - var - } + var +} - public fun read_vector_u256(self: &mut AbiReader): vector { - let mut var = vector[]; - let pos = self.pos; +public fun read_vector_u256(self: &mut AbiReader): vector { + let mut var = vector[]; + let pos = self.pos; - // Move position to the start of the dynamic data - let offset = self.read_u256() as u64; - self.pos = self.head + offset; + // Move position to the start of the dynamic data + let offset = self.read_u256() as u64; + self.pos = self.head + offset; - let length = self.read_u256() as u64; + let length = self.read_u256() as u64; - let mut i = 0; + let mut i = 0; - while (i < length) { - var.push_back(self.read_u256()); - i = i + 1; - }; + while (i < length) { + var.push_back(self.read_u256()); + i = i + 1; + }; - self.pos = pos + 32; + self.pos = pos + 32; - var - } + var +} - /// Decode ABI-encoded 'bytes[]' - public fun read_vector_bytes(self: &mut AbiReader): vector> { - let mut var = vector[]; +/// Decode ABI-encoded 'bytes[]' +public fun read_vector_bytes(self: &mut AbiReader): vector> { + let mut var = vector[]; - let pos = self.pos; - let head = self.head; + let pos = self.pos; + let head = self.head; - // Move position to the start of the dynamic data - let offset = self.read_u256() as u64; - self.pos = head + offset; + // Move position to the start of the dynamic data + let offset = self.read_u256() as u64; + self.pos = head + offset; - let length = self.read_u256() as u64; - self.head = self.pos; + let length = self.read_u256() as u64; + self.head = self.pos; - let mut i = 0; + let mut i = 0; - while (i < length) { - var.push_back(self.read_bytes()); + while (i < length) { + var.push_back(self.read_bytes()); - i = i + 1; - }; + i = i + 1; + }; - // Move position to the next slot - self.pos = pos + 32; - self.head = head; + // Move position to the next slot + self.pos = pos + 32; + self.head = head; - var - } + var +} - public fun write_u256(self: &mut AbiWriter, var: u256): &mut AbiWriter { - let pos = self.pos; - let mut i = 0; +public fun write_u256(self: &mut AbiWriter, var: u256): &mut AbiWriter { + let pos = self.pos; + let mut i = 0; - while (i < 32) { - let exp = ((31 - i) * 8 as u8); - let byte = (var >> exp & 255 as u8); - *&mut self.bytes[i + pos] = byte; - i = i + 1; - }; + while (i < 32) { + let exp = ((31 - i) * 8 as u8); + let byte = (var >> exp & 255 as u8); + *&mut self.bytes[i + pos] = byte; + i = i + 1; + }; - self.pos = pos + 32; + self.pos = pos + 32; - self - } + self +} - public fun write_u8(self: &mut AbiWriter, var: u8): &mut AbiWriter { - self.write_u256(var as u256) - } +public fun write_u8(self: &mut AbiWriter, var: u8): &mut AbiWriter { + self.write_u256(var as u256) +} - public fun write_bytes(self: &mut AbiWriter, var: vector): &mut AbiWriter { - let offset = self.bytes.length() as u256; - self.write_u256(offset); +public fun write_bytes(self: &mut AbiWriter, var: vector): &mut AbiWriter { + let offset = self.bytes.length() as u256; + self.write_u256(offset); - // Write dynamic data length and bytes at the tail - self.append_u256(var.length() as u256); - self.append_bytes(var); + // Write dynamic data length and bytes at the tail + self.append_u256(var.length() as u256); + self.append_bytes(var); - self - } + self +} - public fun write_vector_u256(self: &mut AbiWriter, var: vector): &mut AbiWriter { - let offset = self.bytes.length() as u256; - self.write_u256(offset); +public fun write_vector_u256( + self: &mut AbiWriter, + var: vector, +): &mut AbiWriter { + let offset = self.bytes.length() as u256; + self.write_u256(offset); - let length = var.length(); - self.append_u256(length as u256); + let length = var.length(); + self.append_u256(length as u256); - let mut i = 0; - while (i < length) { - self.append_u256(var[i]); - i = i + 1; - }; + let mut i = 0; + while (i < length) { + self.append_u256(var[i]); + i = i + 1; + }; - self - } + self +} - public fun write_vector_bytes(self: &mut AbiWriter, var: vector>): &mut AbiWriter { - let offset = self.bytes.length() as u256; - self.write_u256(offset); +public fun write_vector_bytes( + self: &mut AbiWriter, + var: vector>, +): &mut AbiWriter { + let offset = self.bytes.length() as u256; + self.write_u256(offset); - let length = var.length(); - self.append_u256(length as u256); + let length = var.length(); + self.append_u256(length as u256); - let mut writer = new_writer(length); - let mut i = 0; + let mut writer = new_writer(length); + let mut i = 0; - while (i < length) { - writer.write_bytes(var[i]); - i = i + 1; - }; + while (i < length) { + writer.write_bytes(var[i]); + i = i + 1; + }; - self.append_bytes(writer.into_bytes()); + self.append_bytes(writer.into_bytes()); - self - } + self +} - // ------------------ - // Internal Functions - // ------------------ +// ------------------ +// Internal Functions +// ------------------ - fun append_u256(self: &mut AbiWriter, var: u256) { - let mut i = 0; - while (i < 32) { - self.bytes.push_back(((var >> ((31 - i) * 8 as u8)) & 255 as u8)); - i = i + 1; - }; - } +fun append_u256(self: &mut AbiWriter, var: u256) { + let mut i = 0; + while (i < 32) { + self.bytes.push_back(((var >> ((31 - i) * 8 as u8)) & 255 as u8)); + i = i + 1; + }; +} - fun append_bytes(self: &mut AbiWriter, var: vector) { - let length = var.length(); - if (length == 0) { - return - }; +fun append_bytes(self: &mut AbiWriter, var: vector) { + let length = var.length(); + if (length == 0) { + return + }; - self.bytes.append(var); + self.bytes.append(var); - let mut i = 0u64; + let mut i = 0u64; - while (i < 31 - (length - 1) % 32) { - self.bytes.push_back(0); - i = i + 1; - }; - } + while (i < 31 - (length - 1) % 32) { + self.bytes.push_back(0); + i = i + 1; + }; +} - fun decode_bytes(self: &mut AbiReader): vector { - let length = self.read_u256() as u64; - let pos = self.pos; +fun decode_bytes(self: &mut AbiReader): vector { + let length = self.read_u256() as u64; + let pos = self.pos; - let mut bytes = vector[]; - let mut i = 0; + let mut bytes = vector[]; + let mut i = 0; - while (i < length) { - bytes.push_back(self.bytes[i + pos]); - i = i + 1; - }; + while (i < length) { + bytes.push_back(self.bytes[i + pos]); + i = i + 1; + }; - bytes - } + bytes +} - // ----- - // Tests - // ----- +// ----- +// Tests +// ----- - #[test] - fun test_u256() { - let input = 56; - let output = x"0000000000000000000000000000000000000000000000000000000000000038"; +#[test] +fun test_u256() { + let input = 56; + let output = x"0000000000000000000000000000000000000000000000000000000000000038"; - let mut writer = new_writer(1); - writer.write_u256(input); - assert!(writer.into_bytes() == output, 0); + let mut writer = new_writer(1); + writer.write_u256(input); + assert!(writer.into_bytes() == output, 0); - let mut reader = new_reader(output); - assert!(reader.read_u256() == input, 1); - } + let mut reader = new_reader(output); + assert!(reader.read_u256() == input, 1); +} - #[test] - fun test_skip_slot() { - let input = 56; - let output = x"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000038"; +#[test] +fun test_skip_slot() { + let input = 56; + let output = x"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000038"; - let mut writer = new_writer(2); - writer.write_u256(1).write_u256(input); - assert!(writer.into_bytes() == output, 0); + let mut writer = new_writer(2); + writer.write_u256(1).write_u256(input); + assert!(writer.into_bytes() == output, 0); - let mut reader = new_reader(output); - reader.skip_slot(); - assert!(reader.read_u256() == input, 1); - } + let mut reader = new_reader(output); + reader.skip_slot(); + assert!(reader.read_u256() == input, 1); +} - #[test] - fun test_read_bytes() { - let input = x"123456"; - let output = x"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000031234560000000000000000000000000000000000000000000000000000000000"; +#[test] +fun test_read_bytes() { + let input = x"123456"; + let output = x"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000031234560000000000000000000000000000000000000000000000000000000000"; - let mut writer = new_writer(1); - writer.write_bytes(input); - assert!(writer.into_bytes() == output, 0); + let mut writer = new_writer(1); + writer.write_bytes(input); + assert!(writer.into_bytes() == output, 0); - let mut reader = new_reader(output); - assert!(reader.read_bytes() == input, 1); - } + let mut reader = new_reader(output); + assert!(reader.read_bytes() == input, 1); +} - #[test] - fun test_read_vector_u256() { - let input = vector[1, 2, 3]; - let output = x"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"; +#[test] +fun test_read_vector_u256() { + let input = vector[1, 2, 3]; + let output = x"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"; - let mut writer = new_writer(1); - writer.write_vector_u256(input); - assert!(writer.into_bytes() == output, 0); + let mut writer = new_writer(1); + writer.write_vector_u256(input); + assert!(writer.into_bytes() == output, 0); - let mut reader = new_reader(output); - assert!(reader.read_vector_u256() == input, 1); - } + let mut reader = new_reader(output); + assert!(reader.read_vector_u256() == input, 1); +} - #[test] - fun test_read_vector_bytes() { - let input = vector[x"01", x"02", x"03", x"04"]; - let output = x"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; +#[test] +fun test_read_vector_bytes() { + let input = vector[x"01", x"02", x"03", x"04"]; + let output = x"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; - let mut writer = new_writer(1); - writer.write_vector_bytes(input); - assert!(writer.into_bytes() == output, 0); + let mut writer = new_writer(1); + writer.write_vector_bytes(input); + assert!(writer.into_bytes() == output, 0); - let mut reader = new_reader(output); - assert!(reader.read_vector_bytes() == input, 1); - } + let mut reader = new_reader(output); + assert!(reader.read_vector_bytes() == input, 1); +} - #[test] - fun test_multiple() { - let (input1, input2, input3, input4) = (1, x"02", vector[3], vector[x"04"]); - let output = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; - - let mut writer = new_writer(4); - writer.write_u256(input1); - writer.write_bytes(input2); - writer.write_vector_u256(input3); - writer.write_vector_bytes(input4); - assert!(writer.into_bytes() == output, 0); - - let mut reader = new_reader(output); - assert!(reader.read_u256() == input1, 1); - assert!(reader.read_bytes() == input2, 2); - assert!(reader.read_vector_u256() == input3, 3); - assert!(reader.read_vector_bytes() == input4, 4); - } +#[test] +fun test_multiple() { + let (input1, input2, input3, input4) = (1, x"02", vector[3], vector[x"04"]); + let output = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; + + let mut writer = new_writer(4); + writer.write_u256(input1); + writer.write_bytes(input2); + writer.write_vector_u256(input3); + writer.write_vector_bytes(input4); + assert!(writer.into_bytes() == output, 0); + + let mut reader = new_reader(output); + assert!(reader.read_u256() == input1, 1); + assert!(reader.read_bytes() == input2, 2); + assert!(reader.read_vector_u256() == input3, 3); + assert!(reader.read_vector_bytes() == input4, 4); } diff --git a/move/axelar_gateway/sources/auth.move b/move/axelar_gateway/sources/auth.move index 681ccef7..017d79c3 100644 --- a/move/axelar_gateway/sources/auth.move +++ b/move/axelar_gateway/sources/auth.move @@ -1,214 +1,234 @@ -module axelar_gateway::auth { - use sui::bcs; - use sui::event; - use sui::table::{Self, Table}; - use sui::clock::Clock; - - use axelar_gateway::weighted_signer::{Self}; - use axelar_gateway::weighted_signers::{WeightedSigners}; - use axelar_gateway::proof::{Proof, Signature}; - use axelar_gateway::bytes32::{Self, Bytes32}; - - // ------ - // Errors - // ------ - const EInvalidWeights: u64 = 0; - const EInvalidThreshold: u64 = 1; - /// For when operators have changed, and proof is no longer valid. - const EInvalidOperators: u64 = 2; - const EInsufficientRotationDelay: u64 = 3; - /// For when number of signatures for the call approvals is below the threshold. - const ELowSignaturesWeight: u64 = 4; - const EMalformedSigners: u64 = 5; - const EInvalidEpoch: u64 = 6; - - // ----- - // Types - // ----- - public struct AxelarSigners has store { - /// Epoch of the signers. - epoch: u64, - /// Epoch for the signers hash. - epoch_by_signers_hash: Table, - /// Domain separator between chains. - domain_separator: Bytes32, - /// Minimum rotation delay. - minimum_rotation_delay: u64, - /// Timestamp of the last rotation. - last_rotation_timestamp: u64, - /// Number of previous signers retained (latest signer isn't included in the count). - previous_signers_retention: u64, - } - - public struct MessageToSign has copy, drop, store { - domain_separator: Bytes32, - signers_hash: Bytes32, - data_hash: Bytes32, - } - - // ------ - // Events - // ------ - /// Emitted when signers are rotated. - public struct SignersRotated has copy, drop { - epoch: u64, - signers_hash: Bytes32, - signers: WeightedSigners, - } - - // ----------------- - // Package Functions - // ----------------- - public(package) fun new(ctx: &mut TxContext): AxelarSigners { - AxelarSigners { - epoch: 0, - epoch_by_signers_hash: table::new(ctx), - domain_separator: bytes32::default(), - minimum_rotation_delay: 0, - last_rotation_timestamp: 0, - previous_signers_retention: 0, - } - } - - public(package) fun setup( - domain_separator: Bytes32, - minimum_rotation_delay: u64, - previous_signers_retention: u64, - initial_signers: WeightedSigners, - clock: &Clock, - ctx: &mut TxContext, - ): AxelarSigners { - let mut signers = AxelarSigners { - epoch: 0, - epoch_by_signers_hash: table::new(ctx), - domain_separator, - minimum_rotation_delay, - last_rotation_timestamp: 0, - previous_signers_retention, - }; - - signers.rotate_signers(clock, initial_signers, false); - - signers - } +module axelar_gateway::auth; + +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::proof::{Proof, Signature}; +use axelar_gateway::weighted_signer; +use axelar_gateway::weighted_signers::WeightedSigners; +use sui::bcs; +use sui::clock::Clock; +use sui::event; +use sui::table::{Self, Table}; + +// ------ +// Errors +// ------ +const EInvalidWeights: u64 = 0; +const EInvalidThreshold: u64 = 1; +/// For when operators have changed, and proof is no longer valid. +const EInvalidOperators: u64 = 2; +const EInsufficientRotationDelay: u64 = 3; +/// For when number of signatures for the call approvals is below the threshold. +const ELowSignaturesWeight: u64 = 4; +const EMalformedSigners: u64 = 5; +const EInvalidEpoch: u64 = 6; + +// ----- +// Types +// ----- +public struct AxelarSigners has store { + /// Epoch of the signers. + epoch: u64, + /// Epoch for the signers hash. + epoch_by_signers_hash: Table, + /// Domain separator between chains. + domain_separator: Bytes32, + /// Minimum rotation delay. + minimum_rotation_delay: u64, + /// Timestamp of the last rotation. + last_rotation_timestamp: u64, + /// Number of previous signers retained (latest signer isn't included in the count). + previous_signers_retention: u64, +} - public(package) fun validate_proof( - self: &AxelarSigners, - data_hash: Bytes32, - proof: Proof, - ): bool { - let signers = proof.signers(); - let signers_hash = signers.hash(); - let signers_epoch = self.epoch_by_signers_hash[signers_hash]; - let current_epoch = self.epoch; - let is_latest_signers = current_epoch == signers_epoch; - - assert!(signers_epoch != 0 && (current_epoch - signers_epoch) <= self.previous_signers_retention, EInvalidEpoch); - - let message = MessageToSign { - domain_separator: self.domain_separator, - signers_hash, - data_hash, - }; +public struct MessageToSign has copy, drop, store { + domain_separator: Bytes32, + signers_hash: Bytes32, + data_hash: Bytes32, +} - validate_signatures( - bcs::to_bytes(&message), - signers, - proof.signatures(), - ); +// ------ +// Events +// ------ +/// Emitted when signers are rotated. +public struct SignersRotated has copy, drop { + epoch: u64, + signers_hash: Bytes32, + signers: WeightedSigners, +} - is_latest_signers +// ----------------- +// Package Functions +// ----------------- +public(package) fun new(ctx: &mut TxContext): AxelarSigners { + AxelarSigners { + epoch: 0, + epoch_by_signers_hash: table::new(ctx), + domain_separator: bytes32::default(), + minimum_rotation_delay: 0, + last_rotation_timestamp: 0, + previous_signers_retention: 0, } +} - public(package) fun rotate_signers(self: &mut AxelarSigners, clock: &Clock, new_signers: WeightedSigners, enforce_rotation_delay: bool) { - validate_signers(&new_signers); - - self.update_rotation_timestamp(clock, enforce_rotation_delay); - - let new_signers_hash = new_signers.hash(); - let epoch = self.epoch + 1; - - // Aborts if the signers already exist - self.epoch_by_signers_hash.add(new_signers_hash, epoch); - self.epoch = epoch; +public(package) fun setup( + domain_separator: Bytes32, + minimum_rotation_delay: u64, + previous_signers_retention: u64, + initial_signers: WeightedSigners, + clock: &Clock, + ctx: &mut TxContext, +): AxelarSigners { + let mut signers = AxelarSigners { + epoch: 0, + epoch_by_signers_hash: table::new(ctx), + domain_separator, + minimum_rotation_delay, + last_rotation_timestamp: 0, + previous_signers_retention, + }; + + signers.rotate_signers(clock, initial_signers, false); + + signers +} - event::emit(SignersRotated { - epoch, - signers_hash: new_signers_hash, - signers: new_signers, - }) - } +public(package) fun validate_proof( + self: &AxelarSigners, + data_hash: Bytes32, + proof: Proof, +): bool { + let signers = proof.signers(); + let signers_hash = signers.hash(); + let signers_epoch = self.epoch_by_signers_hash[signers_hash]; + let current_epoch = self.epoch; + let is_latest_signers = current_epoch == signers_epoch; + + assert!( + signers_epoch != 0 && + (current_epoch - signers_epoch) <= self.previous_signers_retention, + EInvalidEpoch, + ); + + let message = MessageToSign { + domain_separator: self.domain_separator, + signers_hash, + data_hash, + }; + + validate_signatures( + bcs::to_bytes(&message), + signers, + proof.signatures(), + ); + + is_latest_signers +} - // ------------------ - // Internal Functions - // ------------------ +public(package) fun rotate_signers( + self: &mut AxelarSigners, + clock: &Clock, + new_signers: WeightedSigners, + enforce_rotation_delay: bool, +) { + validate_signers(&new_signers); - fun validate_signatures( - message: vector, - signers: &WeightedSigners, - signatures: &vector, - ) { - let signers_length = signers.signers().length(); - let signatures_length = signatures.length(); - assert!(signatures_length != 0, ELowSignaturesWeight); + self.update_rotation_timestamp(clock, enforce_rotation_delay); - let threshold = signers.threshold(); - let mut signer_index = 0; - let mut total_weight = 0; - let mut i = 0; + let new_signers_hash = new_signers.hash(); + let epoch = self.epoch + 1; - while (i < signatures_length) { - let pub_key = signatures[i].recover_pub_key(&message); + // Aborts if the signers already exist + self.epoch_by_signers_hash.add(new_signers_hash, epoch); + self.epoch = epoch; - while (signer_index < signers_length && signers.signers()[signer_index].pub_key() != pub_key) { - signer_index = signer_index + 1; - }; + event::emit(SignersRotated { + epoch, + signers_hash: new_signers_hash, + signers: new_signers, + }) +} - assert!(signer_index < signers_length, EMalformedSigners); +// ------------------ +// Internal Functions +// ------------------ + +fun validate_signatures( + message: vector, + signers: &WeightedSigners, + signatures: &vector, +) { + let signers_length = signers.signers().length(); + let signatures_length = signatures.length(); + assert!(signatures_length != 0, ELowSignaturesWeight); + + let threshold = signers.threshold(); + let mut signer_index = 0; + let mut total_weight = 0; + let mut i = 0; + + while (i < signatures_length) { + let pub_key = signatures[i].recover_pub_key(&message); + + while ( + signer_index < signers_length && + signers.signers()[signer_index].pub_key() != pub_key + ) { + signer_index = signer_index + 1; + }; - total_weight = total_weight + signers.signers()[signer_index].weight(); + assert!(signer_index < signers_length, EMalformedSigners); - if (total_weight >= threshold) { - return - }; + total_weight = total_weight + signers.signers()[signer_index].weight(); - signer_index = signer_index + 1; - i = i + 1; + if (total_weight >= threshold) { + return }; - abort ELowSignaturesWeight - } - - fun validate_signers(signers: &WeightedSigners) { - let signers_length = signers.signers().length(); - assert!(signers_length != 0, EInvalidOperators); + signer_index = signer_index + 1; + i = i + 1; + }; - let mut total_weight = 0; - let mut i = 0; - let mut previous_signer = weighted_signer::default(); + abort ELowSignaturesWeight +} - while (i < signers_length) { - let current_signer = signers.signers()[i]; - assert!(previous_signer.lt(¤t_signer), EInvalidOperators); +fun validate_signers(signers: &WeightedSigners) { + let signers_length = signers.signers().length(); + assert!(signers_length != 0, EInvalidOperators); - let weight = current_signer.weight(); - assert!(weight != 0, EInvalidWeights); + let mut total_weight = 0; + let mut i = 0; + let mut previous_signer = weighted_signer::default(); - total_weight = total_weight + weight; - i = i + 1; - previous_signer = current_signer; - }; + while (i < signers_length) { + let current_signer = signers.signers()[i]; + assert!(previous_signer.lt(¤t_signer), EInvalidOperators); - let threshold = signers.threshold(); - assert!(threshold != 0 && total_weight >= threshold, EInvalidThreshold); - } + let weight = current_signer.weight(); + assert!(weight != 0, EInvalidWeights); - fun update_rotation_timestamp(self: &mut AxelarSigners, clock: &Clock, enforce_rotation_delay: bool) { - let current_timestamp = clock.timestamp_ms(); + total_weight = total_weight + weight; + i = i + 1; + previous_signer = current_signer; + }; - // If the rotation delay is enforced, the current timestamp should be greater than the last rotation timestamp plus the minimum rotation delay. - assert!(!enforce_rotation_delay || current_timestamp >= self.last_rotation_timestamp + self.minimum_rotation_delay, EInsufficientRotationDelay); + let threshold = signers.threshold(); + assert!(threshold != 0 && total_weight >= threshold, EInvalidThreshold); +} - self.last_rotation_timestamp = current_timestamp; - } +fun update_rotation_timestamp( + self: &mut AxelarSigners, + clock: &Clock, + enforce_rotation_delay: bool, +) { + let current_timestamp = clock.timestamp_ms(); + + // If the rotation delay is enforced, the current timestamp should be greater than the last rotation timestamp plus the minimum rotation delay. + assert!( + !enforce_rotation_delay || + current_timestamp >= + self.last_rotation_timestamp + self.minimum_rotation_delay, + EInsufficientRotationDelay, + ); + + self.last_rotation_timestamp = current_timestamp; } diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 29a23430..680c8449 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -3,258 +3,263 @@ /// Channels allow sending and receiving messages between Sui and other chains. /// A channel has a unique id and is treated as the destination address by the Axelar protocol. /// Apps can create a channel and hold on to it for cross-chain messaging. -module axelar_gateway::channel { - use std::ascii::String; - use sui::event; - - // ----- - // Types - // ----- - - /// The Channel object. Acts as a destination for the messages sent through - /// the bridge. The `destination_id` is compared against the `id` of the `Channel` - /// when the message is consumed - public struct Channel has key, store { - /// Unique ID of the channel - id: UID, - } +module axelar_gateway::channel; - /// A HotPotato - this should be received by the application contract and consumed - public struct ApprovedMessage { - /// Source chain axelar-registered name - source_chain: String, - /// Unique ID of the message - message_id: String, - /// Address of the source chain, encoded as a string (e.g. EVM address will be hex string 0x1234...abcd) - source_address: String, - /// The destination Channel's UID - destination_id: address, - /// Message payload - payload: vector, - } +use std::ascii::String; +use sui::event; - // ------ - // Errors - // ------ +// ----- +// Types +// ----- - /// If approved message is consumed by an invalid destination id - const EInvalidDestination: u64 = 0; +/// The Channel object. Acts as a destination for the messages sent through +/// the bridge. The `destination_id` is compared against the `id` of the `Channel` +/// when the message is consumed +public struct Channel has key, store { + /// Unique ID of the channel + id: UID, +} - // ------ - // Events - // ------ +/// A HotPotato - this should be received by the application contract and consumed +public struct ApprovedMessage { + /// Source chain axelar-registered name + source_chain: String, + /// Unique ID of the message + message_id: String, + /// Address of the source chain, encoded as a string (e.g. EVM address will be hex string 0x1234...abcd) + source_address: String, + /// The destination Channel's UID + destination_id: address, + /// Message payload + payload: vector, +} - public struct ChannelCreated has copy, drop { - id: address, - } +// ------ +// Errors +// ------ - public struct ChannelDestroyed has copy, drop { - id: address, - } +/// If approved message is consumed by an invalid destination id +const EInvalidDestination: u64 = 0; - // ---------------- - // Public Functions - // ---------------- +// ------ +// Events +// ------ - /// Create new `Channel` object. - /// Anyone can create their own `Channel` to receive cross-chain messages. - /// In most use cases, a package should create this on init, and hold on to it forever. - public fun new(ctx: &mut TxContext): Channel { - let id = object::new(ctx); +public struct ChannelCreated has copy, drop { + id: address, +} - event::emit(ChannelCreated { id: id.uid_to_address() }); +public struct ChannelDestroyed has copy, drop { + id: address, +} - Channel { - id, - } - } +// ---------------- +// Public Functions +// ---------------- - /// Destroy a `Channel`. Allows apps to destroy the `Channel` object when it's no longer needed. - public fun destroy(self: Channel) { - let Channel { id } = self; +/// Create new `Channel` object. +/// Anyone can create their own `Channel` to receive cross-chain messages. +/// In most use cases, a package should create this on init, and hold on to it forever. +public fun new(ctx: &mut TxContext): Channel { + let id = object::new(ctx); - event::emit(ChannelDestroyed { id: id.uid_to_address() }); + event::emit(ChannelCreated { id: id.uid_to_address() }); - id.delete(); + Channel { + id, } +} - public fun id(self: &Channel): ID { - object::id(self) - } +/// Destroy a `Channel`. Allows apps to destroy the `Channel` object when it's no longer needed. +public fun destroy(self: Channel) { + let Channel { id } = self; - public fun to_address(self: &Channel): address { - object::id_address(self) - } + event::emit(ChannelDestroyed { id: id.uid_to_address() }); - /// Consume an approved message hot potato object intended for this `Channel`. - public fun consume_approved_message( - channel: &Channel, - approved_message: ApprovedMessage - ): (String, String, String, vector) { - let ApprovedMessage { - source_chain, - message_id, - source_address, - destination_id, - payload, - } = approved_message; - - // Check if the message is sent to the correct destination. - assert!(destination_id == object::id_address(channel), EInvalidDestination); - - ( - source_chain, - message_id, - source_address, - payload, - ) - } + id.delete(); +} - // ----------------- - // Package Functions - // ----------------- - - /// Create a new `ApprovedMessage` object to be sent to another chain. Is called - /// by the gateway when a message is "picked up" by the relayer. - public(package) fun create_approved_message( - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload: vector, - ): ApprovedMessage { - ApprovedMessage { - source_chain, - message_id, - source_address, - destination_id, - payload - } - } +public fun id(self: &Channel): ID { + object::id(self) +} - // ----- - // Tests - // ----- - - #[test_only] - public fun new_approved_message( - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload: vector, - ): ApprovedMessage { - ApprovedMessage { - source_chain, - message_id, - source_address, - destination_id, - payload - } - } +public fun to_address(self: &Channel): address { + object::id_address(self) +} - #[test] - fun test_new_and_destroy() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - channel.destroy() - } +/// Consume an approved message hot potato object intended for this `Channel`. +public fun consume_approved_message( + channel: &Channel, + approved_message: ApprovedMessage, +): (String, String, String, vector) { + let ApprovedMessage { + source_chain, + message_id, + source_address, + destination_id, + payload, + } = approved_message; + + // Check if the message is sent to the correct destination. + assert!(destination_id == object::id_address(channel), EInvalidDestination); + + ( + source_chain, + message_id, + source_address, + payload, + ) +} - #[test] - fun test_id() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - assert!(channel.id() == object::id(&channel), 0); - channel.destroy() +// ----------------- +// Package Functions +// ----------------- + +/// Create a new `ApprovedMessage` object to be sent to another chain. Is called +/// by the gateway when a message is "picked up" by the relayer. +public(package) fun create_approved_message( + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload: vector, +): ApprovedMessage { + ApprovedMessage { + source_chain, + message_id, + source_address, + destination_id, + payload, } +} - #[test] - fun test_to_address() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - assert!(channel.to_address() == object::id_address(&channel), 0); - channel.destroy() +// ----- +// Tests +// ----- + +#[test_only] +public fun new_approved_message( + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload: vector, +): ApprovedMessage { + ApprovedMessage { + source_chain, + message_id, + source_address, + destination_id, + payload, } +} - #[test] - fun test_create_approved_message() { - let input_source_chain = std::ascii::string(b"Source Chain"); - let input_message_id = std::ascii::string(b"message id"); - let input_source_address = std::ascii::string(b"Source Address"); - let input_destination_id = @0x5678; - let input_payload = b"payload"; - let approved_message: ApprovedMessage = create_approved_message( - input_source_chain, - input_message_id, - input_source_address, - input_destination_id, - input_payload - ); - - let ApprovedMessage { - source_chain, - message_id, - source_address, - destination_id, - payload - } = approved_message; - assert!(source_chain == input_source_chain, 0); - assert!(message_id == input_message_id, 1); - assert!(source_address == input_source_address, 2); - assert!(destination_id == input_destination_id, 3); - assert!(payload == input_payload, 4); - } +#[test] +fun test_new_and_destroy() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + channel.destroy() +} - #[test] - fun test_consume_approved_message() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - - let input_source_chain = std::ascii::string(b"Source Chain"); - let input_message_id = std::ascii::string(b"message id"); - let input_source_address = std::ascii::string(b"Source Address"); - let input_destination_id = channel.to_address(); - let input_payload = b"payload"; - let approved_message: ApprovedMessage = create_approved_message( - input_source_chain, - input_message_id, - input_source_address, - input_destination_id, - input_payload, - ); - - let (source_chain, message_id, source_address, payload) = channel.consume_approved_message(approved_message); - - assert!(source_chain == input_source_chain, 1); - assert!(message_id == input_message_id, 2); - assert!(source_address == input_source_address, 3); - assert!(payload == input_payload, 4); - - channel.destroy(); - } +#[test] +fun test_id() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + assert!(channel.id() == object::id(&channel), 0); + channel.destroy() +} - #[test] - #[expected_failure(abort_code = EInvalidDestination)] - fun test_consume_approved_message_wrong_destination() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - - let source_chain = std::ascii::string(b"Source Chain"); - let message_id = std::ascii::string(b"message id"); - let source_address = std::ascii::string(b"Source Address"); - let destination_id = @0x5678; - let payload = b"payload"; - - let approved_message = create_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ); - - channel.consume_approved_message(approved_message); - - channel.destroy(); - } +#[test] +fun test_to_address() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + assert!(channel.to_address() == object::id_address(&channel), 0); + channel.destroy() +} + +#[test] +fun test_create_approved_message() { + let input_source_chain = std::ascii::string(b"Source Chain"); + let input_message_id = std::ascii::string(b"message id"); + let input_source_address = std::ascii::string(b"Source Address"); + let input_destination_id = @0x5678; + let input_payload = b"payload"; + let approved_message: ApprovedMessage = create_approved_message( + input_source_chain, + input_message_id, + input_source_address, + input_destination_id, + input_payload, + ); + + let ApprovedMessage { + source_chain, + message_id, + source_address, + destination_id, + payload, + } = approved_message; + assert!(source_chain == input_source_chain, 0); + assert!(message_id == input_message_id, 1); + assert!(source_address == input_source_address, 2); + assert!(destination_id == input_destination_id, 3); + assert!(payload == input_payload, 4); +} + +#[test] +fun test_consume_approved_message() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + + let input_source_chain = std::ascii::string(b"Source Chain"); + let input_message_id = std::ascii::string(b"message id"); + let input_source_address = std::ascii::string(b"Source Address"); + let input_destination_id = channel.to_address(); + let input_payload = b"payload"; + let approved_message: ApprovedMessage = create_approved_message( + input_source_chain, + input_message_id, + input_source_address, + input_destination_id, + input_payload, + ); + + let ( + source_chain, + message_id, + source_address, + payload, + ) = channel.consume_approved_message(approved_message); + + assert!(source_chain == input_source_chain, 1); + assert!(message_id == input_message_id, 2); + assert!(source_address == input_source_address, 3); + assert!(payload == input_payload, 4); + + channel.destroy(); +} + +#[test] +#[expected_failure(abort_code = EInvalidDestination)] +fun test_consume_approved_message_wrong_destination() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + + let source_chain = std::ascii::string(b"Source Chain"); + let message_id = std::ascii::string(b"message id"); + let source_address = std::ascii::string(b"Source Address"); + let destination_id = @0x5678; + let payload = b"payload"; + + let approved_message = create_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ); + + channel.consume_approved_message(approved_message); + + channel.destroy(); } diff --git a/move/axelar_gateway/sources/discovery.move b/move/axelar_gateway/sources/discovery.move index 3d2afc45..41545675 100644 --- a/move/axelar_gateway/sources/discovery.move +++ b/move/axelar_gateway/sources/discovery.move @@ -4,308 +4,311 @@ /// Warning: this solution does allow for any transaction to be executed and /// should be treated as a reference and a temporary solution until there's a /// proper discovery / execution mechanism in place. -module axelar_gateway::discovery { - use std::ascii::{Self, String}; - use std::type_name; - - use sui::table::{Self, Table}; - use sui::bcs::{Self, BCS}; - use sui::hex; - use sui::address; - - use axelar_gateway::channel::Channel; - - /// TypeArgument is not a valid string. - const EInvalidString: u64 = 0; - - /// Channel not found. - const EChannelNotFound: u64 = 1; - - /// A central shared object that stores discovery configuration for the - /// Relayer. The Relayer will use this object to discover and execute the - /// transactions when a message is targeted at specific channel. - public struct RelayerDiscovery has key { - id: UID, - /// A map of channel IDs to the target that needs to be executed by the - /// relayer. There can be only one configuration per channel. - configurations: Table, - } - - public struct Function has store, copy, drop { - package_id: address, - module_name: String, - name: String, - } +module axelar_gateway::discovery; + +use axelar_gateway::channel::Channel; +use std::ascii::{Self, String}; +use std::type_name; +use sui::address; +use sui::bcs::{Self, BCS}; +use sui::hex; +use sui::table::{Self, Table}; + +/// TypeArgument is not a valid string. +const EInvalidString: u64 = 0; + +/// Channel not found. +const EChannelNotFound: u64 = 1; + +/// A central shared object that stores discovery configuration for the +/// Relayer. The Relayer will use this object to discover and execute the +/// transactions when a message is targeted at specific channel. +public struct RelayerDiscovery has key { + id: UID, + /// A map of channel IDs to the target that needs to be executed by the + /// relayer. There can be only one configuration per channel. + configurations: Table, +} - /// Arguments are prefixed with: - /// - 0 for objects followed by exactly 32 bytes that cointain the object id - /// - 1 for pures followed by the bcs encoded form of the pure - /// - 2 for the call contract object, followed by nothing (to be passed into the target function) - /// - 3 for the payload of the contract call (to be passed into the intermediate function) - /// - 4 for an argument returned from a previous move call, followed by a u8 specified which call to get the return of (0 for the first transaction AFTER the one that gets ApprovedMessage out), and then another u8 specifying which argument to input. - public struct MoveCall has store, copy, drop { - function: Function, - arguments: vector>, - type_arguments: vector, - } +public struct Function has store, copy, drop { + package_id: address, + module_name: String, + name: String, +} - public struct Transaction has store, copy, drop { - is_final: bool, - move_calls: vector, - } +/// Arguments are prefixed with: +/// - 0 for objects followed by exactly 32 bytes that cointain the object id +/// - 1 for pures followed by the bcs encoded form of the pure +/// - 2 for the call contract object, followed by nothing (to be passed into the target function) +/// - 3 for the payload of the contract call (to be passed into the intermediate function) +/// - 4 for an argument returned from a previous move call, followed by a u8 specified which call to get the return of (0 for the first transaction AFTER the one that gets ApprovedMessage out), and then another u8 specifying which argument to input. +public struct MoveCall has store, copy, drop { + function: Function, + arguments: vector>, + type_arguments: vector, +} +public struct Transaction has store, copy, drop { + is_final: bool, + move_calls: vector, +} +fun init(ctx: &mut TxContext) { + transfer::share_object(RelayerDiscovery { + id: object::new(ctx), + configurations: table::new(ctx), + }); +} - fun init(ctx: &mut TxContext) { - transfer::share_object(RelayerDiscovery { - id: object::new(ctx), - configurations: table::new(ctx), - }); - } +/// During the creation of the object, the UID should be passed here to +/// receive the Channel and emit an event which will be handled by the +/// Relayer. +/// +/// Example: +/// ``` +/// let id = object::new(ctx); +/// let channel = discovery::create_configuration( +/// relayer_discovery, &id, contents, ctx +/// ); +/// let wrapper = ExampleWrapper { id, channel }; +/// transfer::share_object(wrapper); +/// ``` +/// +/// Note: Wrapper must be a shared object so that Relayer can access it. +public fun register_transaction( + self: &mut RelayerDiscovery, + channel: &Channel, + tx: Transaction, +) { + let channel_id = channel.id(); + if (self.configurations.contains(channel_id)) { + self.configurations.remove(channel_id); + }; + self.configurations.add(channel_id, tx); +} - /// During the creation of the object, the UID should be passed here to - /// receive the Channel and emit an event which will be handled by the - /// Relayer. - /// - /// Example: - /// ``` - /// let id = object::new(ctx); - /// let channel = discovery::create_configuration( - /// relayer_discovery, &id, contents, ctx - /// ); - /// let wrapper = ExampleWrapper { id, channel }; - /// transfer::share_object(wrapper); - /// ``` - /// - /// Note: Wrapper must be a shared object so that Relayer can access it. - public fun register_transaction( - self: &mut RelayerDiscovery, - channel: &Channel, - tx: Transaction, - ) { - let channel_id = channel.id(); - if (self.configurations.contains(channel_id)) { - self.configurations.remove(channel_id); - }; - self.configurations.add(channel_id, tx); - } +/// Get a transaction for a specific channel by the channel `ID`. +public fun get_transaction( + self: &mut RelayerDiscovery, + channel_id: ID, +): Transaction { + assert!(self.configurations.contains(channel_id), EChannelNotFound); + self.configurations[channel_id] +} - /// Get a transaction for a specific channel by the channel `ID`. - public fun get_transaction( - self: &mut RelayerDiscovery, - channel_id: ID, - ): Transaction { - assert!(self.configurations.contains(channel_id), EChannelNotFound); - self.configurations[channel_id] +// === Tx Building === + +public fun new_function( + package_id: address, + module_name: String, + name: String, +): Function { + Function { + package_id, + module_name, + name, } +} - // === Tx Building === - - public fun new_function( - package_id: address, module_name: String, name: String - ): Function { - Function { - package_id, - module_name, - name, - } +public fun new_function_from_bcs(bcs: &mut BCS): Function { + Function { + package_id: bcs::peel_address(bcs), + module_name: ascii::string(bcs::peel_vec_u8(bcs)), + name: ascii::string(bcs::peel_vec_u8(bcs)), } +} - public fun new_function_from_bcs(bcs: &mut BCS): Function { - Function { - package_id: bcs::peel_address(bcs), - module_name: ascii::string(bcs::peel_vec_u8(bcs)), - name: ascii::string(bcs::peel_vec_u8(bcs)), - } +public fun new_move_call( + function: Function, + arguments: vector>, + type_arguments: vector, +): MoveCall { + MoveCall { + function, + arguments, + type_arguments, } +} - public fun new_move_call( - function: Function, - arguments: vector>, - type_arguments: vector - ): MoveCall { - MoveCall { - function, - arguments, - type_arguments, - } +public fun new_move_call_from_bcs(bcs: &mut BCS): MoveCall { + let function = new_function_from_bcs(bcs); + let arguments = bcs.peel_vec_vec_u8(); + let length = bcs.peel_vec_length(); + let mut type_arguments = vector[]; + let mut i = 0; + + while (i < length) { + let mut type_argument = ascii::try_string(bcs.peel_vec_u8()); + assert!(type_argument.is_some(), EInvalidString); + type_arguments.push_back(type_argument.extract()); + i = i + 1; + }; + + MoveCall { + function, + arguments, + type_arguments, } +} - public fun new_move_call_from_bcs(bcs: &mut BCS): MoveCall { - let function = new_function_from_bcs(bcs); - let arguments = bcs.peel_vec_vec_u8(); - let length = bcs.peel_vec_length(); - let mut type_arguments = vector[]; - let mut i = 0; - - while (i < length) { - let mut type_argument = ascii::try_string(bcs.peel_vec_u8()); - assert!(type_argument.is_some(), EInvalidString); - type_arguments.push_back(type_argument.extract()); - i = i + 1; - }; - - MoveCall { - function, - arguments, - type_arguments, - } +public fun new_transaction( + is_final: bool, + move_calls: vector, +): Transaction { + Transaction { + is_final, + move_calls, } +} - public fun new_transaction(is_final: bool, move_calls: vector): Transaction { - Transaction { - is_final, - move_calls, - } - } +public fun new_transaction_from_bcs(bcs: &mut BCS): Transaction { + let is_final = bcs.peel_bool(); + let length = bcs.peel_vec_length(); + let mut move_calls = vector[]; + let mut i = 0; - public fun new_transaction_from_bcs(bcs: &mut BCS): Transaction { - let is_final = bcs.peel_bool(); - let length = bcs.peel_vec_length(); - let mut move_calls = vector[]; - let mut i = 0; - - while (i < length) { - move_calls.push_back(new_move_call_from_bcs(bcs)); - i = i + 1; - }; - - Transaction { - is_final, - move_calls, - } - } + while (i < length) { + move_calls.push_back(new_move_call_from_bcs(bcs)); + i = i + 1; + }; - /// Helper function which returns the package id of from a type. - public fun package_id(): address { - address::from_bytes( - hex::decode( - *ascii::as_bytes( - &type_name::get_address(&type_name::get()) - ) - ) - ) + Transaction { + is_final, + move_calls, } +} - #[test_only] - public fun package_id_from_function(self: &Function): address { - self.package_id - } +/// Helper function which returns the package id of from a type. +public fun package_id(): address { + address::from_bytes( + hex::decode( + *ascii::as_bytes( + &type_name::get_address(&type_name::get()), + ), + ), + ) +} - #[test_only] - public fun module_name(self: &Function): ascii::String { - self.module_name - } +#[test_only] +public fun package_id_from_function(self: &Function): address { + self.package_id +} - #[test_only] - public fun name(self: &Function): ascii::String { - self.name - } +#[test_only] +public fun module_name(self: &Function): ascii::String { + self.module_name +} - #[test_only] - public fun function(self: &MoveCall): Function { - self.function - } +#[test_only] +public fun name(self: &Function): ascii::String { + self.name +} - #[test_only] - public fun arguments(self: &MoveCall): vector> { - self.arguments - } +#[test_only] +public fun function(self: &MoveCall): Function { + self.function +} - #[test_only] - public fun type_arguments(self: &MoveCall): vector { - self.type_arguments - } +#[test_only] +public fun arguments(self: &MoveCall): vector> { + self.arguments +} - #[test_only] - public fun is_final(self: &Transaction): bool { - self.is_final - } +#[test_only] +public fun type_arguments(self: &MoveCall): vector { + self.type_arguments +} - #[test_only] - public fun move_calls(self: &Transaction): vector { - self.move_calls - } +#[test_only] +public fun is_final(self: &Transaction): bool { + self.is_final +} +#[test_only] +public fun move_calls(self: &Transaction): vector { + self.move_calls +} - #[test_only] - public fun new(ctx: &mut TxContext): RelayerDiscovery { - RelayerDiscovery { - id: object::new(ctx), - configurations: table::new(ctx), - } +#[test_only] +public fun new(ctx: &mut TxContext): RelayerDiscovery { + RelayerDiscovery { + id: object::new(ctx), + configurations: table::new(ctx), } +} - #[test] - fun tx_builder() { - let function = new_function( - @0x1, - ascii::string(b"ascii"), - ascii::string(b"string"), - ); - - let _tx = function.new_move_call( - vector[ bcs::to_bytes(&b"some_string") ], // arguments - vector[ ], // type_arguments - ); - } +#[test] +fun tx_builder() { + let function = new_function( + @0x1, + ascii::string(b"ascii"), + ascii::string(b"string"), + ); + + let _tx = function.new_move_call( + vector[bcs::to_bytes(&b"some_string")], + vector[], + ); +} - #[test] - fun test_new_function_from_bcs() { - let package_id = @0x5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962; - let module_name = std::ascii::string(b"module"); - let name = std::ascii::string(b"function"); - let input = x"5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962066d6f64756c650866756e6374696f6e"; - - let function = new_function_from_bcs(&mut bcs::new(input)); - assert!(function.package_id == package_id, 0); - assert!(function.module_name == module_name, 1); - assert!(function.name == name, 2); - } +#[test] +fun test_new_function_from_bcs() { + let package_id = @0x5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962; + let module_name = std::ascii::string(b"module"); + let name = std::ascii::string(b"function"); + let input = x"5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962066d6f64756c650866756e6374696f6e"; + + let function = new_function_from_bcs(&mut bcs::new(input)); + assert!(function.package_id == package_id, 0); + assert!(function.module_name == module_name, 1); + assert!(function.name == name, 2); +} - #[test] - fun test_new_transaction_from_bcs() { - let package_id = @0x5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962; - let module_name = std::ascii::string(b"module"); - let name = std::ascii::string(b"function"); - let arguments = vector[x"1234", x"5678"]; - let type_arguments = vector[ascii::string(b"type1"), ascii::string(b"type2")]; - let input = x"5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962066d6f64756c650866756e6374696f6e0202123402567802057479706531057479706532"; - - let transaction = new_move_call_from_bcs(&mut bcs::new(input)); - assert!(transaction.function.package_id == package_id, 0); - assert!(transaction.function.module_name == module_name, 1); - assert!(transaction.function.name == name, 2); - assert!(transaction.arguments == arguments, 3); - assert!(transaction.type_arguments == type_arguments, 4); - } +#[test] +fun test_new_transaction_from_bcs() { + let package_id = @0x5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962; + let module_name = std::ascii::string(b"module"); + let name = std::ascii::string(b"function"); + let arguments = vector[x"1234", x"5678"]; + let type_arguments = vector[ + ascii::string(b"type1"), + ascii::string(b"type2"), + ]; + let input = x"5f7809eb09754577387a816582ece609511d0262b2c52aa15306083ca3c85962066d6f64756c650866756e6374696f6e0202123402567802057479706531057479706532"; + + let transaction = new_move_call_from_bcs(&mut bcs::new(input)); + assert!(transaction.function.package_id == package_id, 0); + assert!(transaction.function.module_name == module_name, 1); + assert!(transaction.function.name == name, 2); + assert!(transaction.arguments == arguments, 3); + assert!(transaction.type_arguments == type_arguments, 4); +} - #[test] - fun test_register_and_get() { - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - let channel = axelar_gateway::channel::new(ctx); - - let move_call = MoveCall { - function: Function { - package_id: @0x1234, - module_name: std::ascii::string(b"module"), - name: std::ascii::string(b"function"), - }, - arguments: vector::empty>(), - type_arguments: vector::empty(), - }; - let input_transaction = Transaction { - is_final: true, - move_calls: vector[move_call], - }; - - self.register_transaction(&channel, input_transaction); - - let transaction = self.get_transaction(channel.id()); - assert!(transaction == input_transaction, 0); - - sui::test_utils::destroy(self); - sui::test_utils::destroy(channel); - } +#[test] +fun test_register_and_get() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + let channel = axelar_gateway::channel::new(ctx); + + let move_call = MoveCall { + function: Function { + package_id: @0x1234, + module_name: std::ascii::string(b"module"), + name: std::ascii::string(b"function"), + }, + arguments: vector::empty>(), + type_arguments: vector::empty(), + }; + let input_transaction = Transaction { + is_final: true, + move_calls: vector[move_call], + }; + + self.register_transaction(&channel, input_transaction); + + let transaction = self.get_transaction(channel.id()); + assert!(transaction == input_transaction, 0); + + sui::test_utils::destroy(self); + sui::test_utils::destroy(channel); } diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index a4fc3a7a..f264cb62 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -25,332 +25,357 @@ /// - CallApproval is checked for uniqueness (for this channel) /// - CallApproval is checked to match the `Channel`.id /// -module axelar_gateway::gateway { - use std::ascii::{String}; - - use sui::bcs; - use sui::hash; - use sui::table::{Self, Table}; - use sui::address; - use sui::clock::Clock; - - use axelar_gateway::message::{Self, Message}; - use axelar_gateway::bytes32::{Self, Bytes32}; - use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; - use axelar_gateway::auth::{Self, AxelarSigners, validate_proof}; - use axelar_gateway::weighted_signers::{Self, WeightedSigners}; - use axelar_gateway::proof::{Self, Proof}; - - // ------ - // Errors - // ------ - /// Trying to `take_approved_message` for a message that is not approved. - const EMessageNotApproved: u64 = 0; - /// Invalid length of vector - const EInvalidLength: u64 = 1; - /// Remaining data after BCS decoding - const ERemainingData: u64 = 2; - /// Not latest signers - const ENotLatestSigners: u64 = 3; - - // ----- - // Types - // ----- - const COMMAND_TYPE_APPROVE_MESSAGES: u8 = 0; - const COMMAND_TYPE_ROTATE_SIGNERS: u8 = 1; - - const MESSAGE_EXECUTED: address = @0x1; - - /// An object holding the state of the Axelar bridge. - /// The central piece in managing call approval creation and signature verification. - public struct Gateway has key { - id: UID, - operator: address, - messages: Table, - signers: AxelarSigners, - } - - /// The Status of the message. - /// Can be either one of three statuses: - /// - Non-existent: Set to bytes32(0) - /// - Approved: Set to the hash of the message - /// - Executed: Set to bytes32(1) - public struct MessageStatus has store { - status: Bytes32, - } - - // ------------ - // Capabilities - // ------------ - public struct CreatorCap has key, store { - id: UID - } - - // ------ - // Events - // ------ - - /// Emitted when a new message is sent from the SUI network. - public struct ContractCall has copy, drop { - source_id: address, - destination_chain: String, - destination_address: String, - payload: vector, - payload_hash: address, - } - - /// Emitted when a new message is approved by the gateway. - public struct MessageApproved has copy, drop { - message: message::Message, - } - - /// Emitted when a message is taken to be executed by a channel. - public struct MessageExecuted has copy, drop { - message: message::Message, - } - - // ----- - // Setup - // ----- - - /// Init the module by giving a CreatorCap to the sender to allow a full `setup`. - fun init(ctx: &mut TxContext) { - let cap = CreatorCap { - id: object::new(ctx), - }; - - transfer::transfer(cap, ctx.sender()); - } - - /// Setup the module by creating a new Gateway object. - public fun setup( - cap: CreatorCap, - operator: address, - domain_separator: Bytes32, - minimum_rotation_delay: u64, - previous_signers_retention: u64, - initial_signers: vector, - clock: &Clock, - ctx: &mut TxContext - ) { - let CreatorCap { id } = cap; - id.delete(); - - let gateway = Gateway { - id: object::new(ctx), - operator, - messages: table::new(ctx), - signers: auth::setup(domain_separator, minimum_rotation_delay, previous_signers_retention, peel_weighted_signers(initial_signers), clock, ctx), - }; - - // Share the gateway object for anyone to use. - transfer::share_object(gateway); - } - - // ----------- - // Entrypoints - // ----------- - - /// 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, - message_data: vector, - proof_data: vector, - ) { - let messages = peel_messages(*&message_data); - let proof = peel_proof(proof_data); - - let _ = self.signers.validate_proof(data_hash(COMMAND_TYPE_APPROVE_MESSAGES, message_data), proof); - - let mut i = 0; - - while (i < messages.length()) { - self.approve_message(&messages[i]); - - i = i + 1; - }; - } - - /// 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, - clock: &Clock, - new_signers_data: vector, - proof_data: vector, - ctx: &TxContext, - ) { - let weighted_signers = peel_weighted_signers(new_signers_data); - let proof = peel_proof(proof_data); - - let enforce_rotation_delay = ctx.sender() != self.operator; - - let is_latest_signers = self.signers.validate_proof(data_hash(COMMAND_TYPE_ROTATE_SIGNERS, new_signers_data), proof); - assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); - - // This will fail if signers are duplicated - self.signers.rotate_signers(clock, weighted_signers, enforce_rotation_delay); - } - - // ---------------- - // Public Functions - // ---------------- - - /// Call a contract on the destination chain by sending an event from an - /// authorized Channel. Currently we require Channel to be mutable to prevent - /// frozen object scenario or when someone exposes the Channel to the outer - /// world. However, this restriction may be lifted in the future, and having - /// an immutable reference should be enough. - public fun call_contract( - channel: &Channel, - destination_chain: String, - destination_address: String, - payload: vector - ) { - sui::event::emit(ContractCall { - source_id: object::id_address(channel), - destination_chain, - destination_address, - payload, - payload_hash: address::from_bytes(hash::keccak256(&payload)), - }) - } - - public fun is_message_approved( - self: &Gateway, - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload_hash: Bytes32, - ): bool { - let message = message::new( - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - ); - let command_id = message.command_id(); - - self.messages[command_id].status == message.hash() - } - - public fun is_message_executed( - self: &Gateway, - source_chain: String, - message_id: String, - ): bool { - let command_id = message::message_to_command_id( - source_chain, - message_id, +module axelar_gateway::gateway; + +use axelar_gateway::auth::{Self, AxelarSigners, validate_proof}; +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; +use axelar_gateway::message::{Self, Message}; +use axelar_gateway::proof::{Self, Proof}; +use axelar_gateway::weighted_signers::{Self, WeightedSigners}; +use std::ascii::String; +use sui::address; +use sui::bcs; +use sui::clock::Clock; +use sui::hash; +use sui::table::{Self, Table}; + +// ------ +// Errors +// ------ +/// Trying to `take_approved_message` for a message that is not approved. +const EMessageNotApproved: u64 = 0; +/// Invalid length of vector +const EInvalidLength: u64 = 1; +/// Remaining data after BCS decoding +const ERemainingData: u64 = 2; +/// Not latest signers +const ENotLatestSigners: u64 = 3; + +// ----- +// Types +// ----- +const COMMAND_TYPE_APPROVE_MESSAGES: u8 = 0; +const COMMAND_TYPE_ROTATE_SIGNERS: u8 = 1; + +const MESSAGE_EXECUTED: address = @0x1; + +/// An object holding the state of the Axelar bridge. +/// The central piece in managing call approval creation and signature verification. +public struct Gateway has key { + id: UID, + operator: address, + messages: Table, + signers: AxelarSigners, +} + +/// The Status of the message. +/// Can be either one of three statuses: +/// - Non-existent: Set to bytes32(0) +/// - Approved: Set to the hash of the message +/// - Executed: Set to bytes32(1) +public struct MessageStatus has store { + status: Bytes32, +} + +// ------------ +// Capabilities +// ------------ +public struct CreatorCap has key, store { + id: UID, +} + +// ------ +// Events +// ------ + +/// Emitted when a new message is sent from the SUI network. +public struct ContractCall has copy, drop { + source_id: address, + destination_chain: String, + destination_address: String, + payload: vector, + payload_hash: address, +} + +/// Emitted when a new message is approved by the gateway. +public struct MessageApproved has copy, drop { + message: message::Message, +} + +/// Emitted when a message is taken to be executed by a channel. +public struct MessageExecuted has copy, drop { + message: message::Message, +} + +// ----- +// Setup +// ----- + +/// Init the module by giving a CreatorCap to the sender to allow a full `setup`. +fun init(ctx: &mut TxContext) { + let cap = CreatorCap { + id: object::new(ctx), + }; + + transfer::transfer(cap, ctx.sender()); +} + +/// Setup the module by creating a new Gateway object. +public fun setup( + cap: CreatorCap, + operator: address, + domain_separator: Bytes32, + minimum_rotation_delay: u64, + previous_signers_retention: u64, + initial_signers: vector, + clock: &Clock, + ctx: &mut TxContext, +) { + let CreatorCap { id } = cap; + id.delete(); + + let gateway = Gateway { + id: object::new(ctx), + operator, + messages: table::new(ctx), + signers: auth::setup( + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + peel_weighted_signers(initial_signers), + clock, + ctx, + ), + }; + + // Share the gateway object for anyone to use. + transfer::share_object(gateway); +} + +// ----------- +// Entrypoints +// ----------- + +/// 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, + message_data: vector, + proof_data: vector, +) { + let messages = peel_messages(*&message_data); + let proof = peel_proof(proof_data); + + let _ = self + .signers + .validate_proof( + data_hash(COMMAND_TYPE_APPROVE_MESSAGES, message_data), + proof, ); - self.messages[command_id].status == bytes32::new(@0x1) - } - - /// 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, - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload: vector - ): ApprovedMessage { - let command_id = message::message_to_command_id(source_chain, message_id); - - let message = message::new( - source_chain, - message_id, - source_address, - destination_id, - bytes32::from_bytes(hash::keccak256(&payload)), + let mut i = 0; + + while (i < messages.length()) { + self.approve_message(&messages[i]); + + i = i + 1; + }; +} + +/// 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, + clock: &Clock, + new_signers_data: vector, + proof_data: vector, + ctx: &TxContext, +) { + let weighted_signers = peel_weighted_signers(new_signers_data); + let proof = peel_proof(proof_data); + + let enforce_rotation_delay = ctx.sender() != self.operator; + + let is_latest_signers = self + .signers + .validate_proof( + data_hash(COMMAND_TYPE_ROTATE_SIGNERS, new_signers_data), + proof, ); + assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); + + // This will fail if signers are duplicated + self + .signers + .rotate_signers(clock, weighted_signers, enforce_rotation_delay); +} - assert!(self.messages[command_id].status == message.hash(), EMessageNotApproved); +// ---------------- +// Public Functions +// ---------------- + +/// Call a contract on the destination chain by sending an event from an +/// authorized Channel. Currently we require Channel to be mutable to prevent +/// frozen object scenario or when someone exposes the Channel to the outer +/// world. However, this restriction may be lifted in the future, and having +/// an immutable reference should be enough. +public fun call_contract( + channel: &Channel, + destination_chain: String, + destination_address: String, + payload: vector, +) { + sui::event::emit(ContractCall { + source_id: object::id_address(channel), + destination_chain, + destination_address, + payload, + payload_hash: address::from_bytes(hash::keccak256(&payload)), + }) +} - self.messages[command_id].status = bytes32::new(MESSAGE_EXECUTED); +public fun is_message_approved( + self: &Gateway, + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload_hash: Bytes32, +): bool { + let message = message::new( + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + ); + let command_id = message.command_id(); + + self.messages[command_id].status == message.hash() +} - sui::event::emit(MessageExecuted { - message, - }); +public fun is_message_executed( + self: &Gateway, + source_chain: String, + message_id: String, +): bool { + let command_id = message::message_to_command_id( + source_chain, + message_id, + ); + + self.messages[command_id].status == bytes32::new(@0x1) +} - // Friend only. - channel::create_approved_message(source_chain, message_id, source_address, destination_id, payload) - } +/// 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, + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload: vector, +): ApprovedMessage { + let command_id = message::message_to_command_id(source_chain, message_id); + + let message = message::new( + source_chain, + message_id, + source_address, + destination_id, + bytes32::from_bytes(hash::keccak256(&payload)), + ); + + assert!( + self.messages[command_id].status == message.hash(), + EMessageNotApproved, + ); + + self.messages[command_id].status = bytes32::new(MESSAGE_EXECUTED); + + sui::event::emit(MessageExecuted { + message, + }); + + // Friend only. + channel::create_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ) +} - // ----------------- - // Private Functions - // ----------------- +// ----------------- +// Private Functions +// ----------------- - fun peel_messages(message_data: vector): vector { - let mut bcs = bcs::new(message_data); +fun peel_messages(message_data: vector): vector { + let mut bcs = bcs::new(message_data); - let mut messages = vector::empty(); - let mut len = bcs.peel_vec_length(); + let mut messages = vector::empty(); + let mut len = bcs.peel_vec_length(); - while (len > 0) { - messages.push_back(message::peel(&mut bcs)); + while (len > 0) { + messages.push_back(message::peel(&mut bcs)); - len = len - 1; - }; + len = len - 1; + }; - assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); - assert!(messages.length() > 0, EInvalidLength); + assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); + assert!(messages.length() > 0, EInvalidLength); - messages - } + messages +} - fun peel_weighted_signers(weighted_signers_data: vector): WeightedSigners { - let mut bcs = bcs::new(weighted_signers_data); +fun peel_weighted_signers(weighted_signers_data: vector): WeightedSigners { + let mut bcs = bcs::new(weighted_signers_data); - let weighted_signers = weighted_signers::peel(&mut bcs); + let weighted_signers = weighted_signers::peel(&mut bcs); - assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); + assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); - weighted_signers - } + weighted_signers +} - fun peel_proof(proof_data: vector): Proof { - let mut bcs = bcs::new(proof_data); +fun peel_proof(proof_data: vector): Proof { + let mut bcs = bcs::new(proof_data); - let proof = proof::peel(&mut bcs); + let proof = proof::peel(&mut bcs); - assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); + assert!(bcs.into_remainder_bytes().length() == 0, ERemainingData); - proof - } + proof +} - fun data_hash(command_type: u8, data: vector): Bytes32 { - let mut typed_data = vector::singleton(command_type); - typed_data.append(data); +fun data_hash(command_type: u8, data: vector): Bytes32 { + let mut typed_data = vector::singleton(command_type); + typed_data.append(data); - bytes32::from_bytes(hash::keccak256(&typed_data)) - } + bytes32::from_bytes(hash::keccak256(&typed_data)) +} - fun approve_message( - self: &mut Gateway, - message: &message::Message, - ) { - let command_id = message.command_id(); +fun approve_message(self: &mut Gateway, message: &message::Message) { + let command_id = message.command_id(); - // If the message was already approved, ignore it. - if (self.messages.contains(command_id)) { - return - }; + // If the message was already approved, ignore it. + if (self.messages.contains(command_id)) { + return + }; - self.messages.add( + self + .messages + .add( command_id, - MessageStatus { status: message.hash() } + MessageStatus { status: message.hash() }, ); - sui::event::emit(MessageApproved { - message: *message, - }); - } + sui::event::emit(MessageApproved { + message: *message, + }); } diff --git a/move/axelar_gateway/sources/types/bytes32.move b/move/axelar_gateway/sources/types/bytes32.move index 6e58fc32..4804c0ab 100644 --- a/move/axelar_gateway/sources/types/bytes32.move +++ b/move/axelar_gateway/sources/types/bytes32.move @@ -1,62 +1,62 @@ -module axelar_gateway::bytes32 { - use sui::bcs::BCS; - use sui::address; +module axelar_gateway::bytes32; - // ----- - // Types - // ----- +use sui::address; +use sui::bcs::BCS; - public struct Bytes32 has copy, drop, store { - bytes: address, - } +// ----- +// Types +// ----- - // --------- - // Constants - // --------- +public struct Bytes32 has copy, drop, store { + bytes: address, +} - const LENGTH: u64 = 32; +// --------- +// Constants +// --------- - // ---------------- - // Public Functions - // ---------------- +const LENGTH: u64 = 32; - /// Casts an address to a bytes32 - public fun new(bytes: address): Bytes32 { - Bytes32{bytes: bytes} - } +// ---------------- +// Public Functions +// ---------------- - public fun default(): Bytes32 { - Bytes32{bytes: @0x0} - } +/// Casts an address to a bytes32 +public fun new(bytes: address): Bytes32 { + Bytes32 { bytes: bytes } +} - public fun from_bytes(bytes: vector): Bytes32 { - new(address::from_bytes(bytes)) - } +public fun default(): Bytes32 { + Bytes32 { bytes: @0x0 } +} - public fun from_address(addr: address): Bytes32 { - new(addr) - } +public fun from_bytes(bytes: vector): Bytes32 { + new(address::from_bytes(bytes)) +} - public fun to_bytes(self: Bytes32): vector { - self.bytes.to_bytes() - } +public fun from_address(addr: address): Bytes32 { + new(addr) +} - public fun length(_self: &Bytes32): u64 { - LENGTH - } +public fun to_bytes(self: Bytes32): vector { + self.bytes.to_bytes() +} + +public fun length(_self: &Bytes32): u64 { + LENGTH +} - public(package) fun peel(bcs: &mut BCS): Bytes32 { - new(bcs.peel_address()) - } +public(package) fun peel(bcs: &mut BCS): Bytes32 { + new(bcs.peel_address()) +} - // ----- - // Tests - // ----- +// ----- +// Tests +// ----- - #[test] - public fun test_new() { - let actual = new(@0x1); +#[test] +public fun test_new() { + let actual = new(@0x1); - assert!(actual.to_bytes() == @0x1.to_bytes(), 0); - } + assert!(actual.to_bytes() == @0x1.to_bytes(), 0); } diff --git a/move/axelar_gateway/sources/types/message.move b/move/axelar_gateway/sources/types/message.move index 7d31b552..7f05f432 100644 --- a/move/axelar_gateway/sources/types/message.move +++ b/move/axelar_gateway/sources/types/message.move @@ -1,74 +1,76 @@ -module axelar_gateway::message { - use std::ascii::String; - use sui::bcs::{Self, BCS}; - use sui::hash; +module axelar_gateway::message; - use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::bytes32::{Self, Bytes32}; +use std::ascii::String; +use sui::bcs::{Self, BCS}; +use sui::hash; - /// ----- - /// Types - /// ----- - /// Cross chain message type - public struct Message has copy, drop, store { - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload_hash: Bytes32, - } +/// ----- +/// Types +/// ----- +/// Cross chain message type +public struct Message has copy, drop, store { + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload_hash: Bytes32, +} - /// ----------------- - /// Public Functions - /// ----------------- - public fun new( - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload_hash: Bytes32, - ): Message { - Message { - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - } +/// ----------------- +/// Public Functions +/// ----------------- +public fun new( + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload_hash: Bytes32, +): Message { + Message { + source_chain, + message_id, + source_address, + destination_id, + payload_hash, } +} - /// ----------------- - /// Package Functions - /// ----------------- - public(package) fun peel(bcs: &mut BCS): Message { - // TODO: allow UTF-8 strings? Or keep it as more generic bytes? - let source_chain = bcs.peel_vec_u8().to_ascii_string(); - let message_id = bcs.peel_vec_u8().to_ascii_string(); - let source_address = bcs.peel_vec_u8().to_ascii_string(); - let destination_id = bcs.peel_address(); - let payload_hash = bytes32::peel(bcs); +/// ----------------- +/// Package Functions +/// ----------------- +public(package) fun peel(bcs: &mut BCS): Message { + // TODO: allow UTF-8 strings? Or keep it as more generic bytes? + let source_chain = bcs.peel_vec_u8().to_ascii_string(); + let message_id = bcs.peel_vec_u8().to_ascii_string(); + let source_address = bcs.peel_vec_u8().to_ascii_string(); + let destination_id = bcs.peel_address(); + let payload_hash = bytes32::peel(bcs); - Message { - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - } + Message { + source_chain, + message_id, + source_address, + destination_id, + payload_hash, } +} - public(package) fun message_to_command_id(source_chain: String, message_id: String): Bytes32 { - let mut id = source_chain.into_bytes(); - id.append(b"_"); - id.append(message_id.into_bytes()); +public(package) fun message_to_command_id( + source_chain: String, + message_id: String, +): Bytes32 { + let mut id = source_chain.into_bytes(); + id.append(b"_"); + id.append(message_id.into_bytes()); - bytes32::from_bytes(hash::keccak256(&id)) - } + bytes32::from_bytes(hash::keccak256(&id)) +} - public(package) fun command_id(self: &Message): Bytes32 { - message_to_command_id(self.source_chain, self.message_id) - } +public(package) fun command_id(self: &Message): Bytes32 { + message_to_command_id(self.source_chain, self.message_id) +} - public(package) fun hash(self: &Message): Bytes32 { - bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) - } +public(package) fun hash(self: &Message): Bytes32 { + bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) } diff --git a/move/axelar_gateway/sources/types/proof.move b/move/axelar_gateway/sources/types/proof.move index 255e795b..fd2d84d3 100644 --- a/move/axelar_gateway/sources/types/proof.move +++ b/move/axelar_gateway/sources/types/proof.move @@ -1,84 +1,86 @@ -module axelar_gateway::proof { - use sui::bcs::BCS; - use sui::ecdsa_k1 as ecdsa; +module axelar_gateway::proof; - use axelar_gateway::weighted_signers::{Self, WeightedSigners}; +use axelar_gateway::weighted_signers::{Self, WeightedSigners}; +use sui::bcs::BCS; +use sui::ecdsa_k1 as ecdsa; - // ----- - // Types - // ----- - public struct Signature has copy, drop, store { - bytes: vector, - } +// ----- +// Types +// ----- +public struct Signature has copy, drop, store { + bytes: vector, +} - public struct Proof has copy, drop, store { - signers: WeightedSigners, - signatures: vector, - } +public struct Proof has copy, drop, store { + signers: WeightedSigners, + signatures: vector, +} - // --------- - // Constants - // --------- - /// Length of the signature - const SIGNATURE_LENGTH: u64 = 65; - - // ------ - // Errors - // ------ - /// Invalid length of the bytes - const EInvalidLength: u64 = 0; - - // ---------------- - // Public Functions - // ---------------- - /// The signers of the proof - public fun signers(proof: &Proof): &WeightedSigners { - &proof.signers - } +// --------- +// Constants +// --------- +/// Length of the signature +const SIGNATURE_LENGTH: u64 = 65; + +// ------ +// Errors +// ------ +/// Invalid length of the bytes +const EInvalidLength: u64 = 0; + +// ---------------- +// Public Functions +// ---------------- +/// The signers of the proof +public fun signers(proof: &Proof): &WeightedSigners { + &proof.signers +} - /// The proof signatures - public fun signatures(proof: &Proof): &vector { - &proof.signatures - } +/// The proof signatures +public fun signatures(proof: &Proof): &vector { + &proof.signatures +} - // ----------------- - // Package Functions - // ----------------- - public(package) fun new_signature(bytes: vector): Signature { - assert!(bytes.length() == SIGNATURE_LENGTH, EInvalidLength); +// ----------------- +// Package Functions +// ----------------- +public(package) fun new_signature(bytes: vector): Signature { + assert!(bytes.length() == SIGNATURE_LENGTH, EInvalidLength); - Signature { - bytes: bytes, - } + Signature { + bytes: bytes, } +} - /// Recover the public key from an EVM recoverable signature, using keccak256 as the hash function - public(package) fun recover_pub_key(self: &Signature, message: &vector): vector { - ecdsa::secp256k1_ecrecover(&self.bytes, message, 0) - } +/// Recover the public key from an EVM recoverable signature, using keccak256 as the hash function +public(package) fun recover_pub_key( + self: &Signature, + message: &vector, +): vector { + ecdsa::secp256k1_ecrecover(&self.bytes, message, 0) +} - public(package) fun peel_signature(bcs: &mut BCS): Signature { - let bytes = bcs.peel_vec_u8(); +public(package) fun peel_signature(bcs: &mut BCS): Signature { + let bytes = bcs.peel_vec_u8(); - new_signature(bytes) - } + new_signature(bytes) +} - public(package) fun peel(bcs: &mut BCS): Proof { - let signers = weighted_signers::peel(bcs); +public(package) fun peel(bcs: &mut BCS): Proof { + let signers = weighted_signers::peel(bcs); - let mut signatures = vector::empty(); + let mut signatures = vector::empty(); - let mut length = bcs.peel_vec_length(); + let mut length = bcs.peel_vec_length(); - while (length > 0) { - signatures.push_back(peel_signature(bcs)); + while (length > 0) { + signatures.push_back(peel_signature(bcs)); - length = length - 1; - }; + length = length - 1; + }; - Proof { - signers, - signatures, - } + Proof { + signers, + signatures, } } diff --git a/move/axelar_gateway/sources/types/weighted_signer.move b/move/axelar_gateway/sources/types/weighted_signer.move index 6c1dbd62..8e196c8b 100644 --- a/move/axelar_gateway/sources/types/weighted_signer.move +++ b/move/axelar_gateway/sources/types/weighted_signer.move @@ -1,96 +1,96 @@ -module axelar_gateway::weighted_signer { - use sui::bcs::BCS; +module axelar_gateway::weighted_signer; - // --------- - // Constants - // --------- +use sui::bcs::BCS; - /// Length of a public key - const PUB_KEY_LENGTH: u64 = 33; +// --------- +// Constants +// --------- - // ----- - // Types - // ----- +/// Length of a public key +const PUB_KEY_LENGTH: u64 = 33; - public struct WeightedSigner has copy, drop, store { - pub_key: vector, - weight: u128, - } - - public fun pub_key(self: &WeightedSigner): vector { - self.pub_key - } +// ----- +// Types +// ----- - public fun weight(self: &WeightedSigner): u128 { - self.weight - } +public struct WeightedSigner has copy, drop, store { + pub_key: vector, + weight: u128, +} - // ------ - // Errors - // ------ +public fun pub_key(self: &WeightedSigner): vector { + self.pub_key +} - const EInvalidPubKeyLength: u64 = 0; +public fun weight(self: &WeightedSigner): u128 { + self.weight +} - // ----------------- - // Package Functions - // ----------------- +// ------ +// Errors +// ------ - public(package) fun new(pub_key: vector, weight: u128): WeightedSigner { - assert!(pub_key.length() == PUB_KEY_LENGTH, EInvalidPubKeyLength); +const EInvalidPubKeyLength: u64 = 0; - WeightedSigner { pub_key, weight } - } +// ----------------- +// Package Functions +// ----------------- - /// Empty weighted signer - public(package) fun default(): WeightedSigner { - let mut pub_key = @0x0.to_bytes(); - pub_key.push_back(0); +public(package) fun new(pub_key: vector, weight: u128): WeightedSigner { + assert!(pub_key.length() == PUB_KEY_LENGTH, EInvalidPubKeyLength); - WeightedSigner { - pub_key, - weight: 0, - } - } + WeightedSigner { pub_key, weight } +} - public(package) fun peel(bcs: &mut BCS): WeightedSigner { - let pub_key = bcs.peel_vec_u8(); - let weight = bcs.peel_u128(); +/// Empty weighted signer +public(package) fun default(): WeightedSigner { + let mut pub_key = @0x0.to_bytes(); + pub_key.push_back(0); - new(pub_key, weight) + WeightedSigner { + pub_key, + weight: 0, } +} + +public(package) fun peel(bcs: &mut BCS): WeightedSigner { + let pub_key = bcs.peel_vec_u8(); + let weight = bcs.peel_u128(); - /// Check if self.signer is less than other.signer as bytes - public(package) fun lt(self: &WeightedSigner, other: &WeightedSigner): bool { - let mut i = 0; + new(pub_key, weight) +} - while (i < PUB_KEY_LENGTH) { - if (self.pub_key[i] < other.pub_key[i]) { - return true - } else if (self.pub_key[i] > other.pub_key[i]) { - return false - }; +/// Check if self.signer is less than other.signer as bytes +public(package) fun lt(self: &WeightedSigner, other: &WeightedSigner): bool { + let mut i = 0; - i = i + 1; + while (i < PUB_KEY_LENGTH) { + if (self.pub_key[i] < other.pub_key[i]) { + return true + } else if (self.pub_key[i] > other.pub_key[i]) { + return false }; - false - } + i = i + 1; + }; + + false +} - // ----- - // Tests - // ----- +// ----- +// Tests +// ----- - #[test] - fun test_default() { - let signer = default(); +#[test] +fun test_default() { + let signer = default(); - assert!(signer.weight == 0, 0); - assert!(signer.pub_key.length() == PUB_KEY_LENGTH, 1); + assert!(signer.weight == 0, 0); + assert!(signer.pub_key.length() == PUB_KEY_LENGTH, 1); - let mut i = 0; - while (i < PUB_KEY_LENGTH) { - assert!(signer.pub_key[i] == 0, 2); - i = i + 1; - } + let mut i = 0; + while (i < PUB_KEY_LENGTH) { + assert!(signer.pub_key[i] == 0, 2); + i = i + 1; } } diff --git a/move/axelar_gateway/sources/types/weighted_signers.move b/move/axelar_gateway/sources/types/weighted_signers.move index a835c234..1bab579c 100644 --- a/move/axelar_gateway/sources/types/weighted_signers.move +++ b/move/axelar_gateway/sources/types/weighted_signers.move @@ -1,63 +1,62 @@ -module axelar_gateway::weighted_signers { - use sui::bcs::{Self, BCS}; - use sui::hash; +module axelar_gateway::weighted_signers; - use axelar_gateway::bytes32::{Self, Bytes32}; - use axelar_gateway::weighted_signer::{Self, WeightedSigner}; +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::weighted_signer::{Self, WeightedSigner}; +use sui::bcs::{Self, BCS}; +use sui::hash; - public struct WeightedSigners has copy, drop, store { - signers: vector, - threshold: u128, - nonce: Bytes32, - } +public struct WeightedSigners has copy, drop, store { + signers: vector, + threshold: u128, + nonce: Bytes32, +} - /// ------ - /// Errors - /// ------ - /// Invalid length of the bytes - const EInvalidLength: u64 = 0; - - /// ---------------- - /// Public Functions - /// ---------------- - public(package) fun signers(self: &WeightedSigners): vector { - self.signers - } +/// ------ +/// Errors +/// ------ +/// Invalid length of the bytes +const EInvalidLength: u64 = 0; + +/// ---------------- +/// Public Functions +/// ---------------- +public(package) fun signers(self: &WeightedSigners): vector { + self.signers +} - public(package) fun threshold(self: &WeightedSigners): u128 { - self.threshold - } +public(package) fun threshold(self: &WeightedSigners): u128 { + self.threshold +} - public(package) fun nonce(self: &WeightedSigners): Bytes32 { - self.nonce - } +public(package) fun nonce(self: &WeightedSigners): Bytes32 { + self.nonce +} - /// ----------------- - /// Package Functions - /// ----------------- - public(package) fun peel(bcs: &mut BCS): WeightedSigners { - let mut signers = vector::empty(); +/// ----------------- +/// Package Functions +/// ----------------- +public(package) fun peel(bcs: &mut BCS): WeightedSigners { + let mut signers = vector::empty(); - let mut length = bcs.peel_vec_length(); - assert!(length > 0, EInvalidLength); + let mut length = bcs.peel_vec_length(); + assert!(length > 0, EInvalidLength); - while (length > 0) { - signers.push_back(weighted_signer::peel(bcs)); + while (length > 0) { + signers.push_back(weighted_signer::peel(bcs)); - length = length - 1; - }; + length = length - 1; + }; - let threshold = bcs.peel_u128(); - let nonce = bytes32::peel(bcs); + let threshold = bcs.peel_u128(); + let nonce = bytes32::peel(bcs); - WeightedSigners { - signers, - threshold, - nonce, - } + WeightedSigners { + signers, + threshold, + nonce, } +} - public(package) fun hash(self: &WeightedSigners): Bytes32 { - bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) - } +public(package) fun hash(self: &WeightedSigners): Bytes32 { + bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) } diff --git a/move/example/sources/gmp/gmp.move b/move/example/sources/gmp/gmp.move index b6ae319f..620311be 100644 --- a/move/example/sources/gmp/gmp.move +++ b/move/example/sources/gmp/gmp.move @@ -1,84 +1,100 @@ -module example::gmp { - use std::ascii; - use std::ascii::{String}; - use std::type_name; +module example::gmp; - use sui::event; - use sui::address; - use sui::hex; - use sui::coin::{Coin}; - use sui::sui::SUI; +use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; +use axelar_gateway::discovery::{Self, RelayerDiscovery}; +use axelar_gateway::gateway; +use gas_service::gas_service::{Self, GasService}; +use std::ascii::{Self, String}; +use std::type_name; +use sui::address; +use sui::coin::Coin; +use sui::event; +use sui::hex; +use sui::sui::SUI; - use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; - use axelar_gateway::discovery::{Self, RelayerDiscovery}; +public struct Singleton has key { + id: UID, + channel: Channel, +} - use axelar_gateway::gateway; - use gas_service::gas_service::{Self, GasService}; +public struct Executed has copy, drop { + data: vector, +} - public struct Singleton has key { - id: UID, - channel: Channel, - } +fun init(ctx: &mut TxContext) { + let singletonId = object::new(ctx); + let channel = channel::new(ctx); + transfer::share_object(Singleton { + id: singletonId, + channel, + }); +} - public struct Executed has copy, drop { - data: vector, - } - - fun init(ctx: &mut TxContext) { - let singletonId = object::new(ctx); - let channel = channel::new(ctx); - transfer::share_object(Singleton { - id: singletonId, - channel, - }); - } - - public fun register_transaction(discovery: &mut RelayerDiscovery, singleton: &Singleton) { - let mut arguments = vector::empty>(); - let mut arg = vector::singleton(2); - arguments.push_back(arg); - arg = vector::singleton(0); - arg.append(object::id_address(singleton).to_bytes()); - arguments.push_back(arg); - let transaction = discovery::new_transaction( - true, - vector[ - discovery::new_move_call( - discovery::new_function( - address::from_bytes(hex::decode(*ascii::as_bytes(&type_name::get_address(&type_name::get())))), - ascii::string(b"test"), - ascii::string(b"execute") +public fun register_transaction( + discovery: &mut RelayerDiscovery, + singleton: &Singleton, +) { + let mut arguments = vector::empty>(); + let mut arg = vector::singleton(2); + arguments.push_back(arg); + arg = vector::singleton(0); + arg.append(object::id_address(singleton).to_bytes()); + arguments.push_back(arg); + let transaction = discovery::new_transaction( + true, + vector[ + discovery::new_move_call( + discovery::new_function( + address::from_bytes( + hex::decode( + *ascii::as_bytes( + &type_name::get_address( + &type_name::get(), + ), + ), + ), ), - arguments, - vector[], - ) - ] - ); - discovery::register_transaction(discovery, &singleton.channel, transaction); - } + ascii::string(b"test"), + ascii::string(b"execute"), + ), + arguments, + vector[], + ), + ], + ); + discovery::register_transaction(discovery, &singleton.channel, transaction); +} - public fun send_call(singleton: &Singleton, gas_service: &mut GasService, destination_chain: String, destination_address: String, payload: vector, refund_address: address, coin: Coin, params: vector) { - gas_service::pay_gas( - gas_service, - coin, - sui::object::id_address(&singleton.channel), - destination_chain, - destination_address, - payload, - refund_address, - params - ); - gateway::call_contract(&singleton.channel, destination_chain, destination_address, payload); - } +public fun send_call( + singleton: &Singleton, + gas_service: &mut GasService, + destination_chain: String, + destination_address: String, + payload: vector, + refund_address: address, + coin: Coin, + params: vector, +) { + gas_service::pay_gas( + gas_service, + coin, + sui::object::id_address(&singleton.channel), + destination_chain, + destination_address, + payload, + refund_address, + params, + ); + gateway::call_contract( + &singleton.channel, + destination_chain, + destination_address, + payload, + ); +} - public fun execute(call: ApprovedMessage, singleton: &mut Singleton) { - let ( - _, - _, - _, - payload, - ) = singleton.channel.consume_approved_message(call); +public fun execute(call: ApprovedMessage, singleton: &mut Singleton) { + let (_, _, _, payload) = singleton.channel.consume_approved_message(call); - event::emit(Executed { data: payload }); - } - } + event::emit(Executed { data: payload }); +} diff --git a/move/gas_service/sources/gas_service.move b/move/gas_service/sources/gas_service.move index a3b8ff4f..18b9e130 100644 --- a/move/gas_service/sources/gas_service.move +++ b/move/gas_service/sources/gas_service.move @@ -1,341 +1,360 @@ -module gas_service::gas_service { - use std::ascii::String; - - use sui::sui::SUI; - use sui::coin::{Self, Coin}; - use sui::balance::{Self, Balance}; - use sui::address; - use sui::hash::keccak256; - use sui::event; - - // ----- - // Types - // ----- - - public struct GasService has key, store { - id: UID, - balance: Balance, - } - - public struct GasCollectorCap has key, store { - id: UID, - } - - // ------ - // Events - // ------ - - public struct GasPaid has copy, drop { - sender: address, - destination_chain: String, - destination_address: String, - payload_hash: address, - value: u64, - refund_address: address, - params: vector, - } - - public struct GasAdded has copy, drop { - message_id: String, - value: u64, - refund_address: address, - params: vector, - } - - public struct Refunded has copy, drop { - message_id: String, - value: u64, - refund_address: address, - } - - public struct GasCollected has copy, drop { - receiver: address, - value: u64, - } - - // ----- - // Setup - // ----- - - fun init(ctx: &mut TxContext) { - transfer::share_object(GasService { - id: object::new(ctx), - balance: balance::zero(), - }); +module gas_service::gas_service; + +use std::ascii::String; +use sui::address; +use sui::balance::{Self, Balance}; +use sui::coin::{Self, Coin}; +use sui::event; +use sui::hash::keccak256; +use sui::sui::SUI; + +// ----- +// Types +// ----- + +public struct GasService has key, store { + id: UID, + balance: Balance, +} - transfer::public_transfer(GasCollectorCap { - id: object::new(ctx), - }, ctx.sender()); - } - - // ---------------- - // Public Functions - // ---------------- - - /// Pay gas for a contract call. - /// This function is called by the channel that wants to pay gas for a contract call. - /// It can also be called by the user to pay gas for a contract call, while setting the sender as the channel ID. - public fun pay_gas( - self: &mut GasService, - coin: Coin, - sender: address, - destination_chain: String, - destination_address: String, - payload: vector, - refund_address: address, - params: vector, - ) { - let value = coin.value(); - coin::put(&mut self.balance, coin); - let payload_hash = address::from_bytes(keccak256(&payload)); - - event::emit(GasPaid { - sender, - destination_chain, - destination_address, - payload_hash, - value, - refund_address, - params, - }) - } - - /// Add gas for an existing cross-chain contract call. - /// This function can be called by a user who wants to add gas for a contract call with insufficient gas. - public fun add_gas( - self: &mut GasService, - coin: Coin, - message_id: String, - refund_address: address, - params: vector, - ) { - let value = coin.value(); - coin::put(&mut self.balance, coin); - - event::emit(GasAdded { - message_id, - value, - refund_address, - params, - }); - } - - public fun collect_gas(self: &mut GasService, _: &GasCollectorCap, receiver: address, amount: u64, ctx: &mut TxContext) { - transfer::public_transfer( - coin::take(&mut self.balance, amount, ctx), - receiver, - ); - - event::emit(GasCollected { - receiver, - value: amount, - }); - } - - public fun refund(self: &mut GasService, _: &GasCollectorCap, message_id: String, receiver: address, amount: u64, ctx: &mut TxContext) { - transfer::public_transfer( - coin::take(&mut self.balance, amount, ctx), - receiver, - ); - - event::emit(Refunded { - message_id, - value: amount, - refund_address: receiver, - }); - } - - // ----- - // Tests - // ----- - - #[test_only] - fun new(ctx: &mut TxContext): (GasService, GasCollectorCap) { - let service = GasService { - id: object::new(ctx), - balance: balance::zero(), - }; +public struct GasCollectorCap has key, store { + id: UID, +} + +// ------ +// Events +// ------ + +public struct GasPaid has copy, drop { + sender: address, + destination_chain: String, + destination_address: String, + payload_hash: address, + value: u64, + refund_address: address, + params: vector, +} + +public struct GasAdded has copy, drop { + message_id: String, + value: u64, + refund_address: address, + params: vector, +} - let cap = GasCollectorCap { +public struct Refunded has copy, drop { + message_id: String, + value: u64, + refund_address: address, +} + +public struct GasCollected has copy, drop { + receiver: address, + value: u64, +} + +// ----- +// Setup +// ----- + +fun init(ctx: &mut TxContext) { + transfer::share_object(GasService { + id: object::new(ctx), + balance: balance::zero(), + }); + + transfer::public_transfer( + GasCollectorCap { id: object::new(ctx), - }; - - (service, cap) - } - - #[test_only] - fun destroy(self: GasService) { - let GasService { id, balance } = self; - id.delete(); - balance.destroy_for_testing(); - } - - #[test_only] - fun destroy_cap(self: GasCollectorCap) { - let GasCollectorCap { id } = self; - id.delete(); - } - - #[test] - fun test_init() { - let ctx = &mut sui::tx_context::dummy(); - init(ctx); - } - - #[test] - fun test_pay_gas() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - // 2 bytes of the digest for a pseudo-random 1..65,536 - let digest = ctx.digest(); - let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; - let c: Coin = coin::mint_for_testing(value, ctx); - - service.pay_gas( - c, - ctx.sender(), - std::ascii::string(b"destination chain"), - std::ascii::string(b"destination address"), - vector[], - ctx.sender(), - vector[], - ); - - assert!(service.balance.value() == value, 0); - - cap.destroy_cap(); - service.destroy(); - } - - #[test] - fun test_add_gas() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - let digest = ctx.digest(); - let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; // 1..65,536 - let c: Coin = coin::mint_for_testing(value, ctx); - - service.add_gas( - c, - std::ascii::string(b"message id"), - @0x0, - vector[], - ); - - assert!(service.balance.value() == value, 0); - - cap.destroy_cap(); - service.destroy(); - } - - #[test] - fun test_collect_gas() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - let digest = ctx.digest(); - let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; // 1..65,536 - let c: Coin = coin::mint_for_testing(value, ctx); - - service.add_gas( - c, - std::ascii::string(b"message id"), - @0x0, - vector[], - ); - - service.collect_gas( - &cap, - ctx.sender(), - value, - ctx, - ); - - assert!(service.balance.value() == 0, 0); - - cap.destroy_cap(); - service.destroy(); - } - - #[test] - fun test_refund() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - let digest = ctx.digest(); - let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; // 1..65,536 - let c: Coin = coin::mint_for_testing(value, ctx); - - service.add_gas( - c, - std::ascii::string(b"message id"), - @0x0, - vector[], - ); - - service.refund( - &cap, - std::ascii::string(b"message id"), - ctx.sender(), - value, - ctx, - ); - - assert!(service.balance.value() == 0, 0); - - cap.destroy_cap(); - service.destroy(); - } - - #[test] - #[expected_failure(abort_code = sui::balance::ENotEnough)] - fun test_collect_gas_insufficient_balance() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - let digest = ctx.digest(); - let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; // 1..65,536 - let c: Coin = coin::mint_for_testing(value, ctx); - - service.add_gas( - c, - std::ascii::string(b"message id"), - @0x0, - vector[], - ); - - service.collect_gas( - &cap, - ctx.sender(), - value + 1, - ctx, - ); - - cap.destroy_cap(); - service.destroy(); - } - - #[test] - #[expected_failure(abort_code = sui::balance::ENotEnough)] - fun test_refund_insufficient_balance() { - let ctx = &mut sui::tx_context::dummy(); - let (mut service, cap) = new(ctx); - let value = 10; - let c: Coin = coin::mint_for_testing(value, ctx); - - service.add_gas( - c, - std::ascii::string(b"message id"), - @0x0, - vector[], - ); - - service.refund( - &cap, - std::ascii::string(b"message id"), - ctx.sender(), - value + 1, - ctx, - ); - - cap.destroy_cap(); - service.destroy(); - } + }, + ctx.sender(), + ); +} + +// ---------------- +// Public Functions +// ---------------- + +/// Pay gas for a contract call. +/// This function is called by the channel that wants to pay gas for a contract call. +/// It can also be called by the user to pay gas for a contract call, while setting the sender as the channel ID. +public fun pay_gas( + self: &mut GasService, + coin: Coin, + sender: address, + destination_chain: String, + destination_address: String, + payload: vector, + refund_address: address, + params: vector, +) { + let value = coin.value(); + coin::put(&mut self.balance, coin); + let payload_hash = address::from_bytes(keccak256(&payload)); + + event::emit(GasPaid { + sender, + destination_chain, + destination_address, + payload_hash, + value, + refund_address, + params, + }) +} + +/// Add gas for an existing cross-chain contract call. +/// This function can be called by a user who wants to add gas for a contract call with insufficient gas. +public fun add_gas( + self: &mut GasService, + coin: Coin, + message_id: String, + refund_address: address, + params: vector, +) { + let value = coin.value(); + coin::put(&mut self.balance, coin); + + event::emit(GasAdded { + message_id, + value, + refund_address, + params, + }); +} + +public fun collect_gas( + self: &mut GasService, + _: &GasCollectorCap, + receiver: address, + amount: u64, + ctx: &mut TxContext, +) { + transfer::public_transfer( + coin::take(&mut self.balance, amount, ctx), + receiver, + ); + + event::emit(GasCollected { + receiver, + value: amount, + }); +} + +public fun refund( + self: &mut GasService, + _: &GasCollectorCap, + message_id: String, + receiver: address, + amount: u64, + ctx: &mut TxContext, +) { + transfer::public_transfer( + coin::take(&mut self.balance, amount, ctx), + receiver, + ); + + event::emit(Refunded { + message_id, + value: amount, + refund_address: receiver, + }); +} + +// ----- +// Tests +// ----- + +#[test_only] +fun new(ctx: &mut TxContext): (GasService, GasCollectorCap) { + let service = GasService { + id: object::new(ctx), + balance: balance::zero(), + }; + + let cap = GasCollectorCap { + id: object::new(ctx), + }; + + (service, cap) +} + +#[test_only] +fun destroy(self: GasService) { + let GasService { id, balance } = self; + id.delete(); + balance.destroy_for_testing(); +} + +#[test_only] +fun destroy_cap(self: GasCollectorCap) { + let GasCollectorCap { id } = self; + id.delete(); +} + +#[test] +fun test_init() { + let ctx = &mut sui::tx_context::dummy(); + init(ctx); +} + +#[test] +fun test_pay_gas() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + // 2 bytes of the digest for a pseudo-random 1..65,536 + let digest = ctx.digest(); + let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + 1; + let c: Coin = coin::mint_for_testing(value, ctx); + + service.pay_gas( + c, + ctx.sender(), + std::ascii::string(b"destination chain"), + std::ascii::string(b"destination address"), + vector[], + ctx.sender(), + vector[], + ); + + assert!(service.balance.value() == value, 0); + + cap.destroy_cap(); + service.destroy(); +} + +#[test] +fun test_add_gas() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + let digest = ctx.digest(); + let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + + 1; // 1..65,536 + let c: Coin = coin::mint_for_testing(value, ctx); + + service.add_gas( + c, + std::ascii::string(b"message id"), + @0x0, + vector[], + ); + + assert!(service.balance.value() == value, 0); + + cap.destroy_cap(); + service.destroy(); +} + +#[test] +fun test_collect_gas() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + let digest = ctx.digest(); + let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + + 1; // 1..65,536 + let c: Coin = coin::mint_for_testing(value, ctx); + + service.add_gas( + c, + std::ascii::string(b"message id"), + @0x0, + vector[], + ); + + service.collect_gas( + &cap, + ctx.sender(), + value, + ctx, + ); + + assert!(service.balance.value() == 0, 0); + + cap.destroy_cap(); + service.destroy(); +} + +#[test] +fun test_refund() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + let digest = ctx.digest(); + let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + + 1; // 1..65,536 + let c: Coin = coin::mint_for_testing(value, ctx); + + service.add_gas( + c, + std::ascii::string(b"message id"), + @0x0, + vector[], + ); + + service.refund( + &cap, + std::ascii::string(b"message id"), + ctx.sender(), + value, + ctx, + ); + + assert!(service.balance.value() == 0, 0); + + cap.destroy_cap(); + service.destroy(); +} + +#[test] +#[expected_failure(abort_code = sui::balance::ENotEnough)] +fun test_collect_gas_insufficient_balance() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + let digest = ctx.digest(); + let value = (((digest[0] as u16) << 8) | (digest[1] as u16) as u64) + + 1; // 1..65,536 + let c: Coin = coin::mint_for_testing(value, ctx); + + service.add_gas( + c, + std::ascii::string(b"message id"), + @0x0, + vector[], + ); + + service.collect_gas( + &cap, + ctx.sender(), + value + 1, + ctx, + ); + + cap.destroy_cap(); + service.destroy(); +} + +#[test] +#[expected_failure(abort_code = sui::balance::ENotEnough)] +fun test_refund_insufficient_balance() { + let ctx = &mut sui::tx_context::dummy(); + let (mut service, cap) = new(ctx); + let value = 10; + let c: Coin = coin::mint_for_testing(value, ctx); + + service.add_gas( + c, + std::ascii::string(b"message id"), + @0x0, + vector[], + ); + + service.refund( + &cap, + std::ascii::string(b"message id"), + ctx.sender(), + value + 1, + ctx, + ); + + cap.destroy_cap(); + service.destroy(); } diff --git a/move/governance/sources/governance/governance.move b/move/governance/sources/governance/governance.move index 29fb4d71..04164c88 100644 --- a/move/governance/sources/governance/governance.move +++ b/move/governance/sources/governance/governance.move @@ -1,427 +1,504 @@ -module governance::governance { - use std::ascii::String; - use std::type_name; - - use sui::table::{Self, Table}; - use sui::package::{Self, UpgradeCap, UpgradeTicket, UpgradeReceipt}; - use sui::address; - use sui::hex; - - use abi::abi; - use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; - - const EUntrustedAddress: u64 = 0; - const EInvalidMessageType: u64 = 1; - const ENotSelfUpgradeCap: u64 = 2; - const ENotNewPackage: u64 = 3; - - public struct Governance has key, store { - id: UID, - trusted_source_chain: String, - trusted_source_address: String, - message_type: u256, - channel: Channel, - caps: Table, - } - - // This can only be called once since it needs its own upgrade cap which it deletes. - entry fun new( - trusted_source_chain: String, - trusted_source_address: String, - message_type: u256, - upgrade_cap: UpgradeCap, - ctx: &mut TxContext, - ) { - let package_id = object::id_from_bytes( - hex::decode( - type_name::get().get_address().into_bytes() - ) - ); - assert!(upgrade_cap.upgrade_package() == package_id, ENotSelfUpgradeCap); - is_cap_new(&upgrade_cap); - package::make_immutable(upgrade_cap); - - transfer::share_object(Governance{ - id: object::new(ctx), - trusted_source_chain, - trusted_source_address, - message_type, - channel: channel::new(ctx), - caps: table::new(ctx), - }) - } +module governance::governance; + +use abi::abi; +use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; +use std::ascii::String; +use std::type_name; +use sui::address; +use sui::hex; +use sui::package::{Self, UpgradeCap, UpgradeTicket, UpgradeReceipt}; +use sui::table::{Self, Table}; + +const EUntrustedAddress: u64 = 0; +const EInvalidMessageType: u64 = 1; +const ENotSelfUpgradeCap: u64 = 2; +const ENotNewPackage: u64 = 3; + +public struct Governance has key, store { + id: UID, + trusted_source_chain: String, + trusted_source_address: String, + message_type: u256, + channel: Channel, + caps: Table, +} +// This can only be called once since it needs its own upgrade cap which it deletes. +entry fun new( + trusted_source_chain: String, + trusted_source_address: String, + message_type: u256, + upgrade_cap: UpgradeCap, + ctx: &mut TxContext, +) { + let package_id = object::id_from_bytes( + hex::decode(type_name::get().get_address().into_bytes()), + ); + assert!(upgrade_cap.upgrade_package() == package_id, ENotSelfUpgradeCap); + is_cap_new(&upgrade_cap); + package::make_immutable(upgrade_cap); + + transfer::share_object(Governance { + id: object::new(ctx), + trusted_source_chain, + trusted_source_address, + message_type, + channel: channel::new(ctx), + caps: table::new(ctx), + }) +} - public fun is_governance( - self: &Governance, - chain_name: String, - addr: String - ): bool{ - &chain_name == &self.trusted_source_chain && &addr == &self.trusted_source_address - } +public fun is_governance( + self: &Governance, + chain_name: String, + addr: String, +): bool { + &chain_name == + &self.trusted_source_chain && + &addr == &self.trusted_source_address +} - // TODO maybe check that the polcy for the upgrade cap has not been tampered with. - entry fun take_upgrade_cap(self: &mut Governance, upgrade_cap: UpgradeCap) { - is_cap_new(&upgrade_cap); +// TODO maybe check that the polcy for the upgrade cap has not been tampered with. +entry fun take_upgrade_cap(self: &mut Governance, upgrade_cap: UpgradeCap) { + is_cap_new(&upgrade_cap); - self.caps.add( + self + .caps + .add( object::id(&upgrade_cap), upgrade_cap, ) - } - - public fun authorize_upgrade(self: &mut Governance, approved_message: ApprovedMessage): UpgradeTicket { - let (source_chain, _, source_address, payload) = self.channel.consume_approved_message(approved_message); +} - assert!(is_governance(self, source_chain, source_address), EUntrustedAddress); +public fun authorize_upgrade( + self: &mut Governance, + approved_message: ApprovedMessage, +): UpgradeTicket { + let (source_chain, _, source_address, payload) = self + .channel + .consume_approved_message(approved_message); + + assert!( + is_governance(self, source_chain, source_address), + EUntrustedAddress, + ); + + let mut abi = abi::new_reader(payload); + let message_type = abi.read_u256(); + assert!(message_type == self.message_type, EInvalidMessageType); + + let cap_id = object::id_from_address(address::from_u256(abi.read_u256())); + let policy = abi.read_u8(); + let digest = abi.read_bytes(); + + package::authorize_upgrade( + table::borrow_mut(&mut self.caps, cap_id), + policy, + digest, + ) +} - let mut abi = abi::new_reader(payload); - let message_type = abi.read_u256(); - assert!(message_type == self.message_type, EInvalidMessageType); +public fun commit_upgrade(self: &mut Governance, receipt: UpgradeReceipt) { + package::commit_upgrade( + table::borrow_mut( + &mut self.caps, + package::receipt_cap(&receipt), + ), + receipt, + ) +} - let cap_id = object::id_from_address(address::from_u256(abi.read_u256())); - let policy = abi.read_u8(); - let digest = abi.read_bytes(); +fun is_cap_new(cap: &UpgradeCap) { + assert!(package::version(cap) == 1, ENotNewPackage); +} - package::authorize_upgrade( - table::borrow_mut(&mut self.caps, cap_id), - policy, - digest, - ) - } - - public fun commit_upgrade( - self: &mut Governance, - receipt: UpgradeReceipt, - ) { - package::commit_upgrade( - table::borrow_mut( - &mut self.caps, - package::receipt_cap(&receipt), - ), - receipt - ) - } - - - fun is_cap_new(cap: &UpgradeCap) { - assert!(package::version(cap) == 1, ENotNewPackage); - } - - // ----- - // Tests - // ----- - - #[test_only] - use std::ascii; - #[test_only] - use sui::test_scenario; - #[test_only] - use sui::test_utils; - - #[test] - fun test_new() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - let package_id = object::id_from_bytes( - hex::decode( - type_name::get().get_address().into_bytes() - ) - ); - let upgrade_cap = package::test_publish(package_id, &mut ctx); - let initial_owner = @0x1; - let mut scenario = test_scenario::begin(initial_owner); - { - test_scenario::sender(&scenario); - new(trusted_source_chain, trusted_source_address, message_type, upgrade_cap, &mut ctx); - }; - - test_scenario::next_tx(&mut scenario, initial_owner); - { - let governance = test_scenario::take_shared(&scenario); - test_scenario::return_shared(governance); - }; - - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = ENotSelfUpgradeCap)] - fun test_new_incorrect_upgrade_cap() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - let uid = object::new(&mut ctx); - let upgrade_cap = package::test_publish(object::uid_to_inner(&uid), &mut ctx); - new(trusted_source_chain, trusted_source_address, message_type, upgrade_cap, &mut ctx); - - test_utils::destroy(uid); - } - - #[test] - #[expected_failure(abort_code = test_scenario::EEmptyInventory)] - fun test_new_immutable_upgrade() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - let package_id = object::id_from_bytes( - hex::decode( - type_name::get().get_address().into_bytes() - ) - ); - let upgrade_cap = package::test_publish(package_id, &mut ctx); - let initial_owner = @0x1; - let mut scenario = test_scenario::begin(initial_owner); - { - test_scenario::sender(&scenario); - new(trusted_source_chain, trusted_source_address, message_type, upgrade_cap, &mut ctx); - }; - - test_scenario::next_tx(&mut scenario, initial_owner); - { - let upgrade_cap = test_scenario::take_shared(&scenario); - test_scenario::return_shared(upgrade_cap); - }; - - test_scenario::end(scenario); - } - - #[test] - fun test_is_governance() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - - let governance = Governance{ - id: object::new(&mut ctx), +// ----- +// Tests +// ----- + +#[test_only] +use std::ascii; +#[test_only] +use sui::test_scenario; +#[test_only] +use sui::test_utils; + +#[test] +fun test_new() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + let package_id = object::id_from_bytes( + hex::decode(type_name::get().get_address().into_bytes()), + ); + let upgrade_cap = package::test_publish(package_id, &mut ctx); + let initial_owner = @0x1; + let mut scenario = test_scenario::begin(initial_owner); + { + test_scenario::sender(&scenario); + new( trusted_source_chain, trusted_source_address, message_type, - channel: channel::new(&mut ctx), - caps: table::new(&mut ctx), - }; - - assert!(governance.is_governance(trusted_source_chain, trusted_source_address), 1); - - test_utils::destroy(governance); - } - - #[test] - fun test_is_governance_false_argument() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - - let governance = Governance{ - id: object::new(&mut ctx), + upgrade_cap, + &mut ctx, + ); + }; + + test_scenario::next_tx(&mut scenario, initial_owner); + { + let governance = test_scenario::take_shared(&scenario); + test_scenario::return_shared(governance); + }; + + test_scenario::end(scenario); +} + +#[test] +#[expected_failure(abort_code = ENotSelfUpgradeCap)] +fun test_new_incorrect_upgrade_cap() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + let uid = object::new(&mut ctx); + let upgrade_cap = package::test_publish( + object::uid_to_inner(&uid), + &mut ctx, + ); + new( + trusted_source_chain, + trusted_source_address, + message_type, + upgrade_cap, + &mut ctx, + ); + + test_utils::destroy(uid); +} + +#[test] +#[expected_failure(abort_code = test_scenario::EEmptyInventory)] +fun test_new_immutable_upgrade() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + let package_id = object::id_from_bytes( + hex::decode(type_name::get().get_address().into_bytes()), + ); + let upgrade_cap = package::test_publish(package_id, &mut ctx); + let initial_owner = @0x1; + let mut scenario = test_scenario::begin(initial_owner); + { + test_scenario::sender(&scenario); + new( trusted_source_chain, trusted_source_address, message_type, - channel: channel::new(&mut ctx), - caps: table::new(&mut ctx), - }; - - assert!(!governance.is_governance(ascii::string(b"sui"), trusted_source_address), 1); - - test_utils::destroy(governance); - } - - #[test] - fun test_is_cap_new() { - let mut ctx = tx_context::dummy(); - let uid = object::new(&mut ctx); - let upgrade_cap = package::test_publish(object::uid_to_inner(&uid), &mut ctx); - is_cap_new(&upgrade_cap); - - test_utils::destroy(uid); - test_utils::destroy(upgrade_cap); - } - - #[test] - #[expected_failure(abort_code = ENotNewPackage)] - fun test_is_cap_new_upgrade_version() { - let mut ctx = tx_context::dummy(); - let uid = object::new(&mut ctx); - let mut upgrade_cap = package::test_publish(object::uid_to_inner(&uid), &mut ctx); - let upgrade_ticket = package::authorize_upgrade(&mut upgrade_cap, 2, b""); - let upgrade_reciept = package::test_upgrade(upgrade_ticket); - package::commit_upgrade(&mut upgrade_cap, upgrade_reciept); - is_cap_new(&upgrade_cap); - - test_utils::destroy(uid); - test_utils::destroy(upgrade_cap); - } - - #[test] - fun test_take_upgrade_cap() { - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut ctx = tx_context::dummy(); - let package_id = object::id_from_bytes( - hex::decode( - type_name::get().get_address().into_bytes() - ) + upgrade_cap, + &mut ctx, ); - let upgrade_cap = package::test_publish(package_id, &mut ctx); - let initial_owner = @0x1; - let mut scenario = test_scenario::begin(initial_owner); - { - test_scenario::sender(&scenario); - new(trusted_source_chain, trusted_source_address, message_type, upgrade_cap, &mut ctx); - }; - - test_scenario::next_tx(&mut scenario, initial_owner); - { - let mut governance = test_scenario::take_shared(&scenario); - let upgrade_cap = package::test_publish(package_id, &mut ctx); - let upgrade_id = object::id(&upgrade_cap); - take_upgrade_cap(&mut governance, upgrade_cap); - let recieved_upgrade_cap = table::borrow_mut(&mut governance.caps, upgrade_id); - assert!(object::id(recieved_upgrade_cap) == upgrade_id, 1); - test_scenario::return_shared(governance); - }; - - test_scenario::end(scenario); - } - - #[test] - fun test_commit_upgrade() { - let mut ctx = tx_context::dummy(); - let uid = object::new(&mut ctx); - let mut upgrade_cap = package::test_publish(object::uid_to_inner(&uid), &mut ctx); - let upgrade_ticket = package::authorize_upgrade(&mut upgrade_cap, 2, b""); - let upgrade_reciept = package::test_upgrade(upgrade_ticket); - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let message_type = 2; - let mut governance = Governance{ - id: object::new(&mut ctx), + }; + + test_scenario::next_tx(&mut scenario, initial_owner); + { + let upgrade_cap = test_scenario::take_shared(&scenario); + test_scenario::return_shared(upgrade_cap); + }; + + test_scenario::end(scenario); +} + +#[test] +fun test_is_governance() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + + let governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type, + channel: channel::new(&mut ctx), + caps: table::new(&mut ctx), + }; + + assert!( + governance.is_governance(trusted_source_chain, trusted_source_address), + 1, + ); + + test_utils::destroy(governance); +} + +#[test] +fun test_is_governance_false_argument() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + + let governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type, + channel: channel::new(&mut ctx), + caps: table::new(&mut ctx), + }; + + assert!( + !governance.is_governance( + ascii::string(b"sui"), + trusted_source_address, + ), + 1, + ); + + test_utils::destroy(governance); +} + +#[test] +fun test_is_cap_new() { + let mut ctx = tx_context::dummy(); + let uid = object::new(&mut ctx); + let upgrade_cap = package::test_publish( + object::uid_to_inner(&uid), + &mut ctx, + ); + is_cap_new(&upgrade_cap); + + test_utils::destroy(uid); + test_utils::destroy(upgrade_cap); +} + +#[test] +#[expected_failure(abort_code = ENotNewPackage)] +fun test_is_cap_new_upgrade_version() { + let mut ctx = tx_context::dummy(); + let uid = object::new(&mut ctx); + let mut upgrade_cap = package::test_publish( + object::uid_to_inner(&uid), + &mut ctx, + ); + let upgrade_ticket = package::authorize_upgrade(&mut upgrade_cap, 2, b""); + let upgrade_reciept = package::test_upgrade(upgrade_ticket); + package::commit_upgrade(&mut upgrade_cap, upgrade_reciept); + is_cap_new(&upgrade_cap); + + test_utils::destroy(uid); + test_utils::destroy(upgrade_cap); +} + +#[test] +fun test_take_upgrade_cap() { + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut ctx = tx_context::dummy(); + let package_id = object::id_from_bytes( + hex::decode(type_name::get().get_address().into_bytes()), + ); + let upgrade_cap = package::test_publish(package_id, &mut ctx); + let initial_owner = @0x1; + let mut scenario = test_scenario::begin(initial_owner); + { + test_scenario::sender(&scenario); + new( trusted_source_chain, trusted_source_address, message_type, - channel: channel::new(&mut ctx), - caps: table::new(&mut ctx), - }; + upgrade_cap, + &mut ctx, + ); + }; + + test_scenario::next_tx(&mut scenario, initial_owner); + { + let mut governance = test_scenario::take_shared(&scenario); + let upgrade_cap = package::test_publish(package_id, &mut ctx); let upgrade_id = object::id(&upgrade_cap); take_upgrade_cap(&mut governance, upgrade_cap); - commit_upgrade(&mut governance, upgrade_reciept); - let upgrade_cap_return = table::borrow( - &governance.caps, - upgrade_id, - ); - assert!(upgrade_id == object::id(upgrade_cap_return), 1); - assert!(package::version(upgrade_cap_return) == 2, 2); - test_utils::destroy(uid); - test_utils::destroy(governance); - } - - #[test] - fun test_authorize_upgrade() { - let mut ctx = tx_context::dummy(); - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let channale_object = channel::new(&mut ctx); - let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; - let mut abi = abi::new_reader(payload); - let message_type = abi.read_u256(); - let uid = object::new(&mut ctx); - let upgrade_cap = package::test_publish(object::uid_to_inner(&uid), &mut ctx); - let cap_id = object::id_from_address(address::from_u256(abi.read_u256())); - let approved_message = channel::new_approved_message(trusted_source_chain, ascii::string(b"1"), trusted_source_address, object::id_address(&channale_object) , payload); - let mut governance = Governance{ - id: object::new(&mut ctx), - trusted_source_chain, - trusted_source_address, - message_type: message_type, - channel: channale_object, - caps: table::new(&mut ctx), - }; - - governance.caps.add( + let recieved_upgrade_cap = table::borrow_mut( + &mut governance.caps, + upgrade_id, + ); + assert!(object::id(recieved_upgrade_cap) == upgrade_id, 1); + test_scenario::return_shared(governance); + }; + + test_scenario::end(scenario); +} + +#[test] +fun test_commit_upgrade() { + let mut ctx = tx_context::dummy(); + let uid = object::new(&mut ctx); + let mut upgrade_cap = package::test_publish( + object::uid_to_inner(&uid), + &mut ctx, + ); + let upgrade_ticket = package::authorize_upgrade(&mut upgrade_cap, 2, b""); + let upgrade_reciept = package::test_upgrade(upgrade_ticket); + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let message_type = 2; + let mut governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type, + channel: channel::new(&mut ctx), + caps: table::new(&mut ctx), + }; + let upgrade_id = object::id(&upgrade_cap); + take_upgrade_cap(&mut governance, upgrade_cap); + commit_upgrade(&mut governance, upgrade_reciept); + let upgrade_cap_return = table::borrow( + &governance.caps, + upgrade_id, + ); + assert!(upgrade_id == object::id(upgrade_cap_return), 1); + assert!(package::version(upgrade_cap_return) == 2, 2); + test_utils::destroy(uid); + test_utils::destroy(governance); +} + +#[test] +fun test_authorize_upgrade() { + let mut ctx = tx_context::dummy(); + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let channale_object = channel::new(&mut ctx); + let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000"; + let mut abi = abi::new_reader(payload); + let message_type = abi.read_u256(); + let uid = object::new(&mut ctx); + let upgrade_cap = package::test_publish( + object::uid_to_inner(&uid), + &mut ctx, + ); + let cap_id = object::id_from_address(address::from_u256(abi.read_u256())); + let approved_message = channel::new_approved_message( + trusted_source_chain, + ascii::string(b"1"), + trusted_source_address, + object::id_address(&channale_object), + payload, + ); + let mut governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type: message_type, + channel: channale_object, + caps: table::new(&mut ctx), + }; + + governance + .caps + .add( cap_id, upgrade_cap, ); - let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); - assert!(package::ticket_package(&upgrade_ticket) == object::uid_to_inner(&uid), 1); - let policy = abi.read_u8(); - assert!(package::ticket_policy(&upgrade_ticket) == policy, 2); - let digest = abi.read_bytes(); - assert!(package::ticket_digest(&upgrade_ticket) == digest, 3); - test_utils::destroy(upgrade_ticket); - test_utils::destroy(uid); - test_utils::destroy(governance); - } - - #[test] - #[expected_failure(abort_code = EInvalidMessageType)] - fun test_authorize_upgrade_invalid_message_type() { - let mut ctx = tx_context::dummy(); - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let channale_object = channel::new(&mut ctx); - let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0"; - let approved_message = channel::new_approved_message(trusted_source_chain, ascii::string(b"1"), trusted_source_address, object::id_address(&channale_object) , payload); - let mut governance = Governance{ - id: object::new(&mut ctx), - trusted_source_chain, - trusted_source_address, - message_type: 2, - channel: channale_object, - caps: table::new(&mut ctx), - }; - let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); - test_utils::destroy(upgrade_ticket); - test_utils::destroy(governance); - } - - #[test] - #[expected_failure(abort_code = EUntrustedAddress)] - fun test_authorize_upgrade_trusted_address() { - let mut ctx = tx_context::dummy(); - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let channale_object = channel::new(&mut ctx); - let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0"; - let approved_message = channel::new_approved_message(ascii::string(b"sui"), ascii::string(b"1"), ascii::string(b"0x1"), object::id_address(&channale_object) , payload); - let mut governance = Governance{ - id: object::new(&mut ctx), - trusted_source_chain, - trusted_source_address, - message_type: 2, - channel: channale_object, - caps: table::new(&mut ctx), - }; - let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); - test_utils::destroy(upgrade_ticket); - test_utils::destroy(governance); - } - - #[test] - #[expected_failure(abort_code = channel::EInvalidDestination)] - fun test_authorize_invalid_destination_address() { - let mut ctx = tx_context::dummy(); - let trusted_source_chain = ascii::string(b"Axelar"); - let trusted_source_address = ascii::string(b"0x0"); - let channale_object = channel::new(&mut ctx); - let payload = x"01"; - let approved_message = channel::new_approved_message(ascii::string(b"sui"), ascii::string(b"1"), ascii::string(b"0x1"), address::from_u256(2) , payload); - let mut governance = Governance{ - id: object::new(&mut ctx), - trusted_source_chain, - trusted_source_address, - message_type: 2, - channel: channale_object, - caps: table::new(&mut ctx), - }; - let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); - test_utils::destroy(upgrade_ticket); - test_utils::destroy(governance); - } + let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); + assert!( + package::ticket_package(&upgrade_ticket) == object::uid_to_inner(&uid), + 1, + ); + let policy = abi.read_u8(); + assert!(package::ticket_policy(&upgrade_ticket) == policy, 2); + let digest = abi.read_bytes(); + assert!(package::ticket_digest(&upgrade_ticket) == digest, 3); + test_utils::destroy(upgrade_ticket); + test_utils::destroy(uid); + test_utils::destroy(governance); +} + +#[test] +#[expected_failure(abort_code = EInvalidMessageType)] +fun test_authorize_upgrade_invalid_message_type() { + let mut ctx = tx_context::dummy(); + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let channale_object = channel::new(&mut ctx); + let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0"; + let approved_message = channel::new_approved_message( + trusted_source_chain, + ascii::string(b"1"), + trusted_source_address, + object::id_address(&channale_object), + payload, + ); + let mut governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type: 2, + channel: channale_object, + caps: table::new(&mut ctx), + }; + let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); + test_utils::destroy(upgrade_ticket); + test_utils::destroy(governance); +} + +#[test] +#[expected_failure(abort_code = EUntrustedAddress)] +fun test_authorize_upgrade_trusted_address() { + let mut ctx = tx_context::dummy(); + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let channale_object = channel::new(&mut ctx); + let payload = x"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0"; + let approved_message = channel::new_approved_message( + ascii::string(b"sui"), + ascii::string(b"1"), + ascii::string(b"0x1"), + object::id_address(&channale_object), + payload, + ); + let mut governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type: 2, + channel: channale_object, + caps: table::new(&mut ctx), + }; + let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); + test_utils::destroy(upgrade_ticket); + test_utils::destroy(governance); +} + +#[test] +#[expected_failure(abort_code = channel::EInvalidDestination)] +fun test_authorize_invalid_destination_address() { + let mut ctx = tx_context::dummy(); + let trusted_source_chain = ascii::string(b"Axelar"); + let trusted_source_address = ascii::string(b"0x0"); + let channale_object = channel::new(&mut ctx); + let payload = x"01"; + let approved_message = channel::new_approved_message( + ascii::string(b"sui"), + ascii::string(b"1"), + ascii::string(b"0x1"), + address::from_u256(2), + payload, + ); + let mut governance = Governance { + id: object::new(&mut ctx), + trusted_source_chain, + trusted_source_address, + message_type: 2, + channel: channale_object, + caps: table::new(&mut ctx), + }; + let upgrade_ticket = authorize_upgrade(&mut governance, approved_message); + test_utils::destroy(upgrade_ticket); + test_utils::destroy(governance); } diff --git a/move/interchain_token/sources/interchain_token.move b/move/interchain_token/sources/interchain_token.move index 2279654e..d599d600 100644 --- a/move/interchain_token/sources/interchain_token.move +++ b/move/interchain_token/sources/interchain_token.move @@ -1,20 +1,20 @@ -module interchain_token::q { - use sui::coin::{Self}; - use sui::url::{Url}; +module interchain_token::q; - public struct Q has drop {} +use sui::coin; +use sui::url::Url; - fun init(witness: Q, ctx: &mut TxContext) { - let (treasury, metadata) = coin::create_currency( - witness, - 9, - b"Q", - b"Quote", - b"", - option::none(), - ctx - ); - transfer::public_transfer(treasury, tx_context::sender(ctx)); - transfer::public_transfer(metadata, tx_context::sender(ctx)); - } -} \ No newline at end of file +public struct Q has drop {} + +fun init(witness: Q, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 9, + b"Q", + b"Quote", + b"", + option::none(), + ctx, + ); + transfer::public_transfer(treasury, tx_context::sender(ctx)); + transfer::public_transfer(metadata, tx_context::sender(ctx)); +} diff --git a/move/its/sources/address_tracker.move b/move/its/sources/address_tracker.move index cd513b60..089a20cb 100644 --- a/move/its/sources/address_tracker.move +++ b/move/its/sources/address_tracker.move @@ -1,125 +1,125 @@ /// Q: why addresses are stored as Strings? /// Q: why chains are Strings? -module its::address_tracker { - use std::ascii::{String}; +module its::address_tracker; - use sui::table::{Self, Table}; +use std::ascii::String; +use sui::table::{Self, Table}; - /// Attempt to borrow a trusted address but it's not registered. - const ENoAddress: u64 = 0; +/// Attempt to borrow a trusted address but it's not registered. +const ENoAddress: u64 = 0; - /// The interchain address tracker stores the trusted addresses for each chain. - public struct InterchainAddressTracker has store { - trusted_addresses: Table - } +/// The interchain address tracker stores the trusted addresses for each chain. +public struct InterchainAddressTracker has store { + trusted_addresses: Table, +} - /// Get the trusted address for a chain. - public fun get_trusted_address( - self: &InterchainAddressTracker, chain_name: String - ): &String { - assert!(self.trusted_addresses.contains(chain_name), ENoAddress); - &self.trusted_addresses[chain_name] - } +/// Get the trusted address for a chain. +public fun get_trusted_address( + self: &InterchainAddressTracker, + chain_name: String, +): &String { + assert!(self.trusted_addresses.contains(chain_name), ENoAddress); + &self.trusted_addresses[chain_name] +} - /// Check if the given address is trusted for the given chain. - public fun is_trusted_address( - self: &InterchainAddressTracker, chain_name: String, addr: String - ): bool{ - get_trusted_address(self, chain_name) == &addr - } - // === Protected === +/// Check if the given address is trusted for the given chain. +public fun is_trusted_address( + self: &InterchainAddressTracker, + chain_name: String, + addr: String, +): bool { + get_trusted_address(self, chain_name) == &addr +} +// === Protected === - /// Create a new interchain address tracker. - public(package) fun new(ctx: &mut TxContext): InterchainAddressTracker { - InterchainAddressTracker { - trusted_addresses: table::new(ctx), - } +/// Create a new interchain address tracker. +public(package) fun new(ctx: &mut TxContext): InterchainAddressTracker { + InterchainAddressTracker { + trusted_addresses: table::new(ctx), } +} - /// Set the trusted address for a chain or adds it if it doesn't exist. - public(package) fun set_trusted_address( - self: &mut InterchainAddressTracker, - chain_name: String, - trusted_address: String - ) { - if (self.trusted_addresses.contains(chain_name)) { - if (trusted_address.length() == 0) { - self.trusted_addresses.remove(chain_name); - } else { - *&mut self.trusted_addresses[chain_name] = trusted_address; - } +/// Set the trusted address for a chain or adds it if it doesn't exist. +public(package) fun set_trusted_address( + self: &mut InterchainAddressTracker, + chain_name: String, + trusted_address: String, +) { + if (self.trusted_addresses.contains(chain_name)) { + if (trusted_address.length() == 0) { + self.trusted_addresses.remove(chain_name); } else { - if (trusted_address.length() > 0) { - self.trusted_addresses.add(chain_name, trusted_address); - } + *&mut self.trusted_addresses[chain_name] = trusted_address; + } + } else { + if (trusted_address.length() > 0) { + self.trusted_addresses.add(chain_name, trusted_address); } } +} - #[test] - fun test_address_tracker() { - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - let chain1 = std::ascii::string(b"chain1"); - let chain2 = std::ascii::string(b"chain2"); - let address1 = std::ascii::string(b"address1"); - let address2 = std::ascii::string(b"address2"); - - self.set_trusted_address(chain1, address1); - self.set_trusted_address(chain2, address2); - - assert!(self.get_trusted_address(chain1) == &address1, 0); - assert!(self.get_trusted_address(chain2) == &address2, 1); +#[test] +fun test_address_tracker() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + let chain1 = std::ascii::string(b"chain1"); + let chain2 = std::ascii::string(b"chain2"); + let address1 = std::ascii::string(b"address1"); + let address2 = std::ascii::string(b"address2"); - assert!(self.is_trusted_address(chain1, address1) == true, 2); - assert!(self.is_trusted_address(chain1, address2) == false, 3); - assert!(self.is_trusted_address(chain2, address1) == false, 4); - assert!(self.is_trusted_address(chain2, address2) == true, 5); + self.set_trusted_address(chain1, address1); + self.set_trusted_address(chain2, address2); + assert!(self.get_trusted_address(chain1) == &address1, 0); + assert!(self.get_trusted_address(chain2) == &address2, 1); - self.set_trusted_address(chain1, address2); - self.set_trusted_address(chain2, address1); + assert!(self.is_trusted_address(chain1, address1) == true, 2); + assert!(self.is_trusted_address(chain1, address2) == false, 3); + assert!(self.is_trusted_address(chain2, address1) == false, 4); + assert!(self.is_trusted_address(chain2, address2) == true, 5); - assert!(self.get_trusted_address(chain1) == &address2, 6); - assert!(self.get_trusted_address(chain2) == &address1, 7); + self.set_trusted_address(chain1, address2); + self.set_trusted_address(chain2, address1); - assert!(self.is_trusted_address(chain1, address1) == false, 8); - assert!(self.is_trusted_address(chain1, address2) == true, 9); - assert!(self.is_trusted_address(chain2, address1) == true, 10); - assert!(self.is_trusted_address(chain2, address2) == false, 11); + assert!(self.get_trusted_address(chain1) == &address2, 6); + assert!(self.get_trusted_address(chain2) == &address1, 7); - sui::test_utils::destroy(self); - } + assert!(self.is_trusted_address(chain1, address1) == false, 8); + assert!(self.is_trusted_address(chain1, address2) == true, 9); + assert!(self.is_trusted_address(chain2, address1) == true, 10); + assert!(self.is_trusted_address(chain2, address2) == false, 11); - #[test] - #[expected_failure(abort_code = ENoAddress)] - fun test_address_tracker_get_no_address() { - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - let chain1 = std::ascii::string(b"chain1"); - let address1 = std::ascii::string(b"address1"); + sui::test_utils::destroy(self); +} - self.set_trusted_address(chain1, address1); - self.set_trusted_address(chain1, std::ascii::string(b"")); +#[test] +#[expected_failure(abort_code = ENoAddress)] +fun test_address_tracker_get_no_address() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + let chain1 = std::ascii::string(b"chain1"); + let address1 = std::ascii::string(b"address1"); - self.get_trusted_address(chain1); + self.set_trusted_address(chain1, address1); + self.set_trusted_address(chain1, std::ascii::string(b"")); - sui::test_utils::destroy(self); - } + self.get_trusted_address(chain1); + sui::test_utils::destroy(self); +} - #[test] - #[expected_failure(abort_code = ENoAddress)] - fun test_address_tracker_check_no_address() { - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - let chain1 = std::ascii::string(b"chain1"); - let address1 = std::ascii::string(b"address1"); +#[test] +#[expected_failure(abort_code = ENoAddress)] +fun test_address_tracker_check_no_address() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + let chain1 = std::ascii::string(b"chain1"); + let address1 = std::ascii::string(b"address1"); - self.set_trusted_address(chain1, address1); - self.set_trusted_address(chain1, std::ascii::string(b"")); + self.set_trusted_address(chain1, address1); + self.set_trusted_address(chain1, std::ascii::string(b"")); - self.is_trusted_address(chain1, address1); + self.is_trusted_address(chain1, address1); - sui::test_utils::destroy(self); - } + sui::test_utils::destroy(self); } diff --git a/move/its/sources/coin_info.move b/move/its/sources/coin_info.move index f7eebf00..1001f630 100644 --- a/move/its/sources/coin_info.move +++ b/move/its/sources/coin_info.move @@ -1,80 +1,88 @@ - - /// Defines the `CoinInfo` type which allows to store information about a coin: /// either derived from `CoinMetadata` or manually provided. -module its::coin_info { - use std::ascii; - use std::string::String; - - use sui::coin::CoinMetadata; +module its::coin_info; - use its::utils; +use its::utils; +use std::ascii; +use std::string::String; +use sui::coin::CoinMetadata; - public struct CoinInfo has store { - name: String, - symbol: ascii::String, - decimals: u8, - remote_decimals: u8, - metadata: Option>, - } +public struct CoinInfo has store { + name: String, + symbol: ascii::String, + decimals: u8, + remote_decimals: u8, + metadata: Option>, +} - /// Create a new coin info from the given name, symbol and decimals. - public fun from_info( - name: String, symbol: ascii::String, decimals: u8, remote_decimals: u8, - ): CoinInfo { - CoinInfo { - name, - symbol, - decimals, - remote_decimals, - metadata: option::none(), - } +/// Create a new coin info from the given name, symbol and decimals. +public fun from_info( + name: String, + symbol: ascii::String, + decimals: u8, + remote_decimals: u8, +): CoinInfo { + CoinInfo { + name, + symbol, + decimals, + remote_decimals, + metadata: option::none(), } +} - /// Create a new coin info from the given `CoinMetadata` object. - public fun from_metadata(metadata: CoinMetadata, remote_decimals: u8): CoinInfo { - CoinInfo { - name: metadata.get_name(), - symbol: metadata.get_symbol(), - decimals: metadata.get_decimals(), - remote_decimals, - metadata: option::some(metadata), - } +/// Create a new coin info from the given `CoinMetadata` object. +public fun from_metadata( + metadata: CoinMetadata, + remote_decimals: u8, +): CoinInfo { + CoinInfo { + name: metadata.get_name(), + symbol: metadata.get_symbol(), + decimals: metadata.get_decimals(), + remote_decimals, + metadata: option::some(metadata), } +} - // === Views === +// === Views === - public fun name(self: &CoinInfo): String { - self.name - } +public fun name(self: &CoinInfo): String { + self.name +} - public fun symbol(self: &CoinInfo): ascii::String { - self.symbol - } +public fun symbol(self: &CoinInfo): ascii::String { + self.symbol +} - public fun decimals(self: &CoinInfo): u8 { - self.decimals - } +public fun decimals(self: &CoinInfo): u8 { + self.decimals +} - public fun remote_decimals(self: &CoinInfo): u8 { - self.remote_decimals - } +public fun remote_decimals(self: &CoinInfo): u8 { + self.remote_decimals +} - public fun scaling(self: &CoinInfo): u256 { - utils::pow(10, self.remote_decimals - self.decimals) - } +public fun scaling(self: &CoinInfo): u256 { + utils::pow(10, self.remote_decimals - self.decimals) +} - public fun metadata(self: &CoinInfo): &Option> { - &self.metadata - } +public fun metadata(self: &CoinInfo): &Option> { + &self.metadata +} - #[test_only] - public fun drop(coin_info: CoinInfo) { - let CoinInfo {name: _, symbol: _, decimals: _, remote_decimals: _, metadata } = coin_info; - if (metadata.is_some()) { - abort 0 - } else { - metadata.destroy_none() - } +#[test_only] +public fun drop(coin_info: CoinInfo) { + let CoinInfo { + name: _, + symbol: _, + decimals: _, + remote_decimals: _, + metadata, + } = coin_info; + if (metadata.is_some()) { + abort 0 + } else { + metadata.destroy_none() } } diff --git a/move/its/sources/coin_management.move b/move/its/sources/coin_management.move index f3ca48f6..866f18b7 100644 --- a/move/its/sources/coin_management.move +++ b/move/its/sources/coin_management.move @@ -1,210 +1,226 @@ +module its::coin_management; + +use axelar_gateway::channel::Channel; +use its::flow_limit::{Self, FlowLimit}; +use sui::balance::{Self, Balance}; +use sui::clock::Clock; +use sui::coin::{Self, TreasuryCap, Coin}; + + + +/// Trying to add a distributor to a `CoinManagement` that does not +/// have a `TreasuryCap`. +const EDistributorNeedsTreasuryCap: u64 = 0; +const ENotOperator: u64 = 1; + +/// Struct that stores information about the ITS Coin. +public struct CoinManagement has store { + treasury_cap: Option>, + balance: Option>, + distributor: Option
, + operator: Option
, + flow_limit: FlowLimit, + scaling: u256, + dust: u256, +} - -module its::coin_management { - - use sui::coin::{Self, TreasuryCap, Coin}; - use sui::balance::{Self, Balance}; - use sui::clock::Clock; - - use axelar_gateway::channel::Channel; - - use its::flow_limit::{Self, FlowLimit}; - - /// Trying to add a distributor to a `CoinManagement` that does not - /// have a `TreasuryCap`. - const EDistributorNeedsTreasuryCap: u64 = 0; - const ENotOperator: u64 = 1; - - /// Struct that stores information about the ITS Coin. - public struct CoinManagement has store { - treasury_cap: Option>, - balance: Option>, - distributor: Option
, - operator: Option
, - flow_limit: FlowLimit, - scaling: u256, - dust: u256, - } - - /// Create a new `CoinManagement` with a `TreasuryCap`. - /// This type of `CoinManagement` allows minting and burning of coins. - public fun new_with_cap(treasury_cap: TreasuryCap): CoinManagement { - CoinManagement { - treasury_cap: option::some(treasury_cap), - balance: option::none(), - distributor: option::none(), - operator: option::none(), - flow_limit: flow_limit::new(), - scaling: 0, // placeholder, this gets edited when a coin is registered. - dust: 0, - } - } - - /// Create a new `CoinManagement` with a `Balance`. - /// The stored `Balance` can be used to take and put coins. - public fun new_locked(): CoinManagement { - CoinManagement { - treasury_cap: option::none(), - balance: option::some(balance::zero()), - distributor: option::none(), - operator: option::none(), - flow_limit: flow_limit::new(), - scaling: 0, // placeholder, this gets edited when a coin is registered. - dust: 0, - } - } - - /// Adds the distributor address to the `CoinManagement`. - /// Only works for a `CoinManagement` with a `TreasuryCap`. - public fun add_distributor(self: &mut CoinManagement, distributor: address) { - assert!(has_capability(self), EDistributorNeedsTreasuryCap); - self.distributor.fill(distributor); +/// Create a new `CoinManagement` with a `TreasuryCap`. +/// This type of `CoinManagement` allows minting and burning of coins. +public fun new_with_cap(treasury_cap: TreasuryCap): CoinManagement { + CoinManagement { + treasury_cap: option::some(treasury_cap), + balance: option::none(), + distributor: option::none(), + operator: option::none(), + flow_limit: flow_limit::new(), + scaling: 0, // placeholder, this gets edited when a coin is registered. + dust: 0, } +} - /// Adds the distributor address to the `CoinManagement`. - /// Only works for a `CoinManagement` with a `TreasuryCap`. - public fun add_operator(self: &mut CoinManagement, operator: address) { - self.operator.fill(operator); +/// Create a new `CoinManagement` with a `Balance`. +/// The stored `Balance` can be used to take and put coins. +public fun new_locked(): CoinManagement { + CoinManagement { + treasury_cap: option::none(), + balance: option::some(balance::zero()), + distributor: option::none(), + operator: option::none(), + flow_limit: flow_limit::new(), + scaling: 0, // placeholder, this gets edited when a coin is registered. + dust: 0, } +} +/// Adds the distributor address to the `CoinManagement`. +/// Only works for a `CoinManagement` with a `TreasuryCap`. +public fun add_distributor( + self: &mut CoinManagement, + distributor: address, +) { + assert!(has_capability(self), EDistributorNeedsTreasuryCap); + self.distributor.fill(distributor); +} - /// Adds a rate limit to the `CoinManagement`. - /// Note that this rate limit will be calculated for the remote decimals of the token, not for the native decimals. - /// To be used by the designated operator of the contract. - public fun set_flow_limit(self: &mut CoinManagement, channel: &Channel, flow_limit: u64) { - assert!(self.operator.contains(&channel.to_address()), ENotOperator); - self.flow_limit.set_flow_limit(flow_limit); - } +/// Adds the distributor address to the `CoinManagement`. +/// Only works for a `CoinManagement` with a `TreasuryCap`. +public fun add_operator(self: &mut CoinManagement, operator: address) { + self.operator.fill(operator); +} - // === Protected Methods === - - /// Takes the given amount of Coins from user. Returns the amount that the ITS is supposed to give on other chains. - public(package) fun take_coin(self: &mut CoinManagement, to_take: Coin, clock: &Clock): u256 { - self.flow_limit.add_flow_out(to_take.value(), clock); - let amount = (to_take.value() as u256) * self.scaling; - if (has_capability(self)) { - self.burn(to_take); - } else { - self.balance - .borrow_mut() - .join(to_take.into_balance()); - }; - amount - } +/// Adds a rate limit to the `CoinManagement`. +/// Note that this rate limit will be calculated for the remote decimals of the token, not for the native decimals. +/// To be used by the designated operator of the contract. +public fun set_flow_limit( + self: &mut CoinManagement, + channel: &Channel, + flow_limit: u64, +) { + assert!(self.operator.contains(&channel.to_address()), ENotOperator); + self.flow_limit.set_flow_limit(flow_limit); +} - /// Withdraws or mints the given amount of coins. Any leftover amount from previous transfers is added to the coin here. - public(package) fun give_coin( - self: &mut CoinManagement, mut amount: u256, clock: &Clock, ctx: &mut TxContext - ): Coin { - amount = amount + self.dust; - self.dust = amount % self.scaling; - let sui_amount = ( amount / self.scaling as u64); - self.flow_limit.add_flow_out(sui_amount, clock); - if (has_capability(self)) { - self.mint(sui_amount, ctx) - } else { - coin::take(self.balance.borrow_mut(), sui_amount, ctx) - } - } +// === Protected Methods === + +/// Takes the given amount of Coins from user. Returns the amount that the ITS is supposed to give on other chains. +public(package) fun take_coin( + self: &mut CoinManagement, + to_take: Coin, + clock: &Clock, +): u256 { + self.flow_limit.add_flow_out(to_take.value(), clock); + let amount = (to_take.value() as u256) * self.scaling; + if (has_capability(self)) { + self.burn(to_take); + } else { + self.balance.borrow_mut().join(to_take.into_balance()); + }; + amount +} - public(package) fun set_scaling(self: &mut CoinManagement, scaling: u256) { - self.scaling = scaling; +/// Withdraws or mints the given amount of coins. Any leftover amount from previous transfers is added to the coin here. +public(package) fun give_coin( + self: &mut CoinManagement, + mut amount: u256, + clock: &Clock, + ctx: &mut TxContext, +): Coin { + amount = amount + self.dust; + self.dust = amount % self.scaling; + let sui_amount = (amount / self.scaling as u64); + self.flow_limit.add_flow_out(sui_amount, clock); + if (has_capability(self)) { + self.mint(sui_amount, ctx) + } else { + coin::take(self.balance.borrow_mut(), sui_amount, ctx) } +} - // helper function to mint as a distributor. - public(package) fun mint(self: &mut CoinManagement, amount: u64, ctx: &mut TxContext): Coin { - self.treasury_cap - .borrow_mut() - .mint(amount, ctx) - } +public(package) fun set_scaling( + self: &mut CoinManagement, + scaling: u256, +) { + self.scaling = scaling; +} - // helper function to burn as a distributor. - public(package) fun burn(self: &mut CoinManagement, coin: Coin) { - self.treasury_cap - .borrow_mut() - .burn(coin); - } - // === Views === +// helper function to mint as a distributor. +public(package) fun mint( + self: &mut CoinManagement, + amount: u64, + ctx: &mut TxContext, +): Coin { + self.treasury_cap.borrow_mut().mint(amount, ctx) +} - /// Checks if the given address is a `distributor`. - public fun is_distributor(self: &CoinManagement, distributor: address): bool { - &distributor == self.distributor.borrow() - } +// helper function to burn as a distributor. +public(package) fun burn(self: &mut CoinManagement, coin: Coin) { + self.treasury_cap.borrow_mut().burn(coin); +} +// === Views === + +/// Checks if the given address is a `distributor`. +public fun is_distributor( + self: &CoinManagement, + distributor: address, +): bool { + &distributor == self.distributor.borrow() +} - /// Returns true if the coin management has a `TreasuryCap`. - public fun has_capability(self: &CoinManagement): bool { - self.treasury_cap.is_some() - } +/// Returns true if the coin management has a `TreasuryCap`. +public fun has_capability(self: &CoinManagement): bool { + self.treasury_cap.is_some() +} - - #[test_only] - public struct COIN_MANAGEMENT has drop {} - - #[test_only] - fun create_currency(): (TreasuryCap, sui::coin::CoinMetadata) { - sui::coin::create_currency( - sui::test_utils::create_one_time_witness(), - 6, - b"TT", - b"Test Token", - b"", - option::none(), - &mut sui::tx_context::dummy() - ) - } - #[test] - fun test_take_coin() { - let (mut cap, metadata) = create_currency(); - let ctx = &mut sui::tx_context::dummy(); - let amount1 = 10; - let amount2 = 20; - - let mut coin = cap.mint(amount1, ctx); - let mut management1 = new_locked(); - let clock = sui::clock::create_for_testing(ctx); - management1.take_coin(coin, &clock); - - assert!(management1.balance.borrow().value() == amount1, 0); - - coin = cap.mint(amount2, ctx); - let mut management2 = new_with_cap(cap); - management2.take_coin(coin, &clock); - - sui::test_utils::destroy(metadata); - sui::test_utils::destroy(management1); - sui::test_utils::destroy(management2); - sui::test_utils::destroy(clock); - } +#[test_only] +public struct COIN_MANAGEMENT has drop {} + +#[test_only] +fun create_currency(): ( + TreasuryCap, + sui::coin::CoinMetadata, +) { + sui::coin::create_currency( + sui::test_utils::create_one_time_witness(), + 6, + b"TT", + b"Test Token", + b"", + option::none(), + &mut sui::tx_context::dummy(), + ) +} +#[test] +fun test_take_coin() { + let (mut cap, metadata) = create_currency(); + let ctx = &mut sui::tx_context::dummy(); + let amount1 = 10; + let amount2 = 20; + + let mut coin = cap.mint(amount1, ctx); + let mut management1 = new_locked(); + let clock = sui::clock::create_for_testing(ctx); + management1.take_coin(coin, &clock); + + assert!(management1.balance.borrow().value() == amount1, 0); + + coin = cap.mint(amount2, ctx); + let mut management2 = new_with_cap(cap); + management2.take_coin(coin, &clock); + + sui::test_utils::destroy(metadata); + sui::test_utils::destroy(management1); + sui::test_utils::destroy(management2); + sui::test_utils::destroy(clock); +} - #[test] - fun test_give_coin() { - let (mut cap, metadata) = create_currency(); - let ctx = &mut sui::tx_context::dummy(); - let amount1 = 10; - let amount2 = 20; +#[test] +fun test_give_coin() { + let (mut cap, metadata) = create_currency(); + let ctx = &mut sui::tx_context::dummy(); + let amount1 = 10; + let amount2 = 20; - let mut coin = cap.mint(amount1, ctx); - let mut management1 = new_locked(); - management1.scaling = 1; - let clock = sui::clock::create_for_testing(ctx); - management1.take_coin(coin, &clock); - coin = management1.give_coin((amount1 as u256), &clock, ctx); + let mut coin = cap.mint(amount1, ctx); + let mut management1 = new_locked(); + management1.scaling = 1; + let clock = sui::clock::create_for_testing(ctx); + management1.take_coin(coin, &clock); + coin = management1.give_coin((amount1 as u256), &clock, ctx); - assert!(management1.balance.borrow().value() == 0, 0); - assert!(coin.value() == amount1, 0); + assert!(management1.balance.borrow().value() == 0, 0); + assert!(coin.value() == amount1, 0); - sui::test_utils::destroy(coin); + sui::test_utils::destroy(coin); - let mut management2 = new_with_cap(cap); - management2.scaling = 1; - coin = management2.give_coin((amount2 as u256), &clock, ctx); + let mut management2 = new_with_cap(cap); + management2.scaling = 1; + coin = management2.give_coin((amount2 as u256), &clock, ctx); - assert!(coin.value() == amount2, 1); + assert!(coin.value() == amount2, 1); - sui::test_utils::destroy(coin); - sui::test_utils::destroy(metadata); - sui::test_utils::destroy(management1); - sui::test_utils::destroy(management2); - sui::test_utils::destroy(clock); - } + sui::test_utils::destroy(coin); + sui::test_utils::destroy(metadata); + sui::test_utils::destroy(management1); + sui::test_utils::destroy(management2); + sui::test_utils::destroy(clock); } diff --git a/move/its/sources/discovery.move b/move/its/sources/discovery.move index 4fe27264..d6daa146 100644 --- a/move/its/sources/discovery.move +++ b/move/its/sources/discovery.move @@ -1,129 +1,151 @@ +module its::discovery; + +use abi::abi::{Self, AbiReader}; +use axelar_gateway::discovery::{ + Self, + RelayerDiscovery, + Transaction, + package_id +}; +use its::its::ITS; +use its::token_id::{Self, TokenId}; +use std::ascii; +use std::type_name; +use sui::address; + +const EUnsupportedMessageType: u64 = 0; +const EInvalidMessageType: u64 = 0; + +const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; +const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; +//const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; + +public fun get_interchain_transfer_info( + payload: vector, +): (TokenId, address, u64, vector) { + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + reader.skip_slot(); // skip source_address + let destination = address::from_bytes(reader.read_bytes()); + let amount = (reader.read_u256() as u64); + let data = reader.read_bytes(); + + ( + token_id, + destination, + amount, + data, + ) +} +public fun register_transaction( + self: &mut ITS, + discovery: &mut RelayerDiscovery, +) { + self.set_relayer_discovery_id(discovery); + let mut arg = vector[0]; + arg.append(object::id(self).to_bytes()); + + let arguments = vector[ + arg, + vector[3], + ]; + + let function = discovery::new_function( + package_id(), + ascii::string(b"discovery"), + ascii::string(b"get_call_info"), + ); + + let move_call = discovery::new_move_call( + function, + arguments, + vector[], + ); + + discovery.register_transaction( + self.channel(), + discovery::new_transaction( + false, + vector[move_call], + ), + ); +} -module its::discovery { - use std::ascii; - use std::type_name; - - use sui::address; - - use abi::abi::{Self, AbiReader}; - - use axelar_gateway::discovery::{Self, RelayerDiscovery, Transaction, package_id}; - - use its::its::ITS; - use its::token_id::{Self, TokenId}; - - const EUnsupportedMessageType: u64 = 0; - const EInvalidMessageType: u64 = 0; - - const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; - const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; - //const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; - - public fun get_interchain_transfer_info(payload: vector): (TokenId, address, u64, vector) { - let mut reader = abi::new_reader(payload); - assert!(reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, EInvalidMessageType); - - let token_id = token_id::from_u256(reader.read_u256()); - reader.skip_slot(); // skip source_address - let destination = address::from_bytes(reader.read_bytes()); - let amount = (reader.read_u256() as u64); - let data = reader.read_bytes(); +public fun get_call_info(self: &ITS, payload: vector): Transaction { + let mut reader = abi::new_reader(payload); + let message_type = reader.read_u256(); - ( - token_id, - destination, - amount, - data, - ) + if (message_type == MESSAGE_TYPE_INTERCHAIN_TRANSFER) { + get_interchain_transfer_tx(self, &mut reader) + } else { + assert!( + message_type == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, + EUnsupportedMessageType, + ); + get_deploy_interchain_token_tx(self, &mut reader) } +} - public fun register_transaction(self: &mut ITS, discovery: &mut RelayerDiscovery) { - self.set_relayer_discovery_id(discovery); +fun get_interchain_transfer_tx( + self: &ITS, + reader: &mut AbiReader, +): Transaction { + let token_id = token_id::from_u256(reader.read_u256()); + reader.skip_slot(); // skip source_address + let destination_address = address::from_bytes(reader.read_bytes()); + reader.skip_slot(); // skip amount + let data = reader.read_bytes(); + + if (data.is_empty()) { let mut arg = vector[0]; - arg.append(object::id(self).to_bytes()); + arg.append(object::id_address(self).to_bytes()); + + let type_name = self.get_registered_coin_type(token_id); let arguments = vector[ arg, - vector[3] + vector[2], + vector[0, 6], ]; - let function = discovery::new_function( - package_id(), - ascii::string(b"discovery"), - ascii::string(b"get_call_info") - ); - - let move_call = discovery::new_move_call( - function, - arguments, - vector[], - ); - - discovery.register_transaction(self.channel(), discovery::new_transaction( - false, - vector[move_call], - )); - } - - public fun get_call_info(self: &ITS, payload: vector): Transaction { - let mut reader = abi::new_reader(payload); - let message_type = reader.read_u256(); - - if (message_type == MESSAGE_TYPE_INTERCHAIN_TRANSFER) { - get_interchain_transfer_tx(self, &mut reader) - } else { - assert!(message_type == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, EUnsupportedMessageType); - get_deploy_interchain_token_tx(self, &mut reader) - } - } - - fun get_interchain_transfer_tx(self: &ITS, reader: &mut AbiReader): Transaction { - let token_id = token_id::from_u256(reader.read_u256()); - reader.skip_slot(); // skip source_address - let destination_address = address::from_bytes(reader.read_bytes()); - reader.skip_slot(); // skip amount - let data = reader.read_bytes(); - - if (data.is_empty()) { - let mut arg = vector[0]; - arg.append(object::id_address(self).to_bytes()); - - let type_name = self.get_registered_coin_type(token_id); - - let arguments = vector[ - arg, - vector[2], - vector[0, 6], - ]; - - - discovery::new_transaction( - true, - vector[discovery::new_move_call( + discovery::new_transaction( + true, + vector[ + discovery::new_move_call( discovery::new_function( package_id(), ascii::string(b"service"), - ascii::string(b"receive_interchain_transfer") + ascii::string(b"receive_interchain_transfer"), ), arguments, - vector[ type_name::into_string(*type_name) ], - )], - ) - } else { - let mut discovery_arg = vector[0]; - discovery_arg.append(self.relayer_discovery_id().id_to_address().to_bytes()); - - let mut channel_id_arg = vector[1]; - channel_id_arg.append(destination_address.to_bytes()); - - discovery::new_transaction( - false, - vector[discovery::new_move_call( + vector[type_name::into_string(*type_name)], + ), + ], + ) + } else { + let mut discovery_arg = vector[0]; + discovery_arg.append(self + .relayer_discovery_id() + .id_to_address() + .to_bytes()); + + let mut channel_id_arg = vector[1]; + channel_id_arg.append(destination_address.to_bytes()); + + discovery::new_transaction( + false, + vector[ + discovery::new_move_call( discovery::new_function( package_id(), ascii::string(b"discovery"), - ascii::string(b"get_transaction") + ascii::string(b"get_transaction"), ), vector[ discovery_arg, @@ -131,233 +153,269 @@ module its::discovery { vector[0, 6], ], vector[], - )], - ) - } - } - - fun get_deploy_interchain_token_tx(self: &ITS, reader: &mut AbiReader): Transaction { - let mut arg = vector[0]; - arg.append(object::id_address(self).to_bytes()); - - let arguments = vector[ - arg, - vector[2] - ]; - - reader.skip_slot(); // skip token_id - reader.skip_slot(); // skip _name - let symbol = ascii::string(reader.read_bytes()); - let decimals = (reader.read_u256() as u8); - reader.skip_slot(); // skip distributor - - let type_name = self.get_unregistered_coin_type(&symbol, decimals); - - let move_call = discovery::new_move_call( - discovery::new_function( - package_id(), - ascii::string(b"service"), - ascii::string(b"receive_deploy_interchain_token") - ), - arguments, - vector[ type_name::into_string(*type_name) ], - ); - - discovery::new_transaction( - true, - vector[ move_call ], + ), + ], ) } +} - #[test_only] - fun get_initial_tx(self: &ITS): Transaction { - let mut arg = vector[0]; - arg.append(sui::bcs::to_bytes(&object::id(self))); - - let arguments = vector[ - arg, - vector[3] - ]; - - let function = discovery::new_function( - discovery::package_id(), - ascii::string(b"discovery"), - ascii::string(b"get_call_info") - ); - - let move_call = discovery::new_move_call( - function, - arguments, - vector[], - ); - - discovery::new_transaction( - false, - vector[move_call], - ) - } +fun get_deploy_interchain_token_tx( + self: &ITS, + reader: &mut AbiReader, +): Transaction { + let mut arg = vector[0]; + arg.append(object::id_address(self).to_bytes()); - #[test] - fun test_discovery_initial() { - let ctx = &mut sui::tx_context::dummy(); - let mut its = its::its::new(); - let mut discovery = axelar_gateway::discovery::new(ctx); + let arguments = vector[ + arg, + vector[2], + ]; - register_transaction(&mut its, &mut discovery); + reader.skip_slot(); // skip token_id + reader.skip_slot(); // skip _name + let symbol = ascii::string(reader.read_bytes()); + let decimals = (reader.read_u256() as u8); + reader.skip_slot(); // skip distributor - assert!(discovery.get_transaction(its.channel_id()) == get_initial_tx(&its), 0); - assert!(its.relayer_discovery_id() == object::id(&discovery), 1); + let type_name = self.get_unregistered_coin_type(&symbol, decimals); - sui::test_utils::destroy(its); - sui::test_utils::destroy(discovery); - } + let move_call = discovery::new_move_call( + discovery::new_function( + package_id(), + ascii::string(b"service"), + ascii::string(b"receive_deploy_interchain_token"), + ), + arguments, + vector[type_name::into_string(*type_name)], + ); + + discovery::new_transaction( + true, + vector[move_call], + ) +} - #[test] - fun test_discovery_interchain_transfer() { - let ctx = &mut sui::tx_context::dummy(); - let mut its = its::its::new(); - let mut discovery = axelar_gateway::discovery::new(ctx); - - register_transaction(&mut its, &mut discovery); - - let token_id = @0x1234; - let source_address = b"source address"; - let target_channel = @0x5678; - let amount = 1905; - let data = b""; - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(address::to_u256(token_id)) - .write_bytes(source_address) - .write_bytes(target_channel.to_bytes()) - .write_u256(amount) - .write_bytes(data); - let payload = writer.into_bytes(); - - let type_arg = std::type_name::get(); - its.test_add_registered_coin_type(its::token_id::from_address(token_id), type_arg); - let tx_block = get_call_info(&its, payload); - - let mut reader = abi::new_reader(payload); - reader.skip_slot(); // skip message_type - - assert!(tx_block == get_interchain_transfer_tx(&its, &mut reader), 1); - assert!(tx_block.is_final() && tx_block.move_calls().length() == 1, 2); - - let call_info = tx_block.move_calls().pop_back(); - - assert!(call_info.function().package_id_from_function() == package_id(), 3); - assert!(call_info.function().module_name() == ascii::string(b"service"), 4); - assert!(call_info.function().name() == ascii::string(b"receive_interchain_transfer"), 5); - let mut arg = vector[0]; - arg.append(object::id_address(&its).to_bytes()); +#[test_only] +fun get_initial_tx(self: &ITS): Transaction { + let mut arg = vector[0]; + arg.append(sui::bcs::to_bytes(&object::id(self))); + + let arguments = vector[ + arg, + vector[3], + ]; + + let function = discovery::new_function( + discovery::package_id(), + ascii::string(b"discovery"), + ascii::string(b"get_call_info"), + ); + + let move_call = discovery::new_move_call( + function, + arguments, + vector[], + ); + + discovery::new_transaction( + false, + vector[move_call], + ) +} - let arguments = vector[ - arg, - vector[2], - vector[0, 6], - ]; - assert!(call_info.arguments() == arguments, 6); - assert!(call_info.type_arguments() == vector[type_arg.into_string()], 7); +#[test] +fun test_discovery_initial() { + let ctx = &mut sui::tx_context::dummy(); + let mut its = its::its::new(); + let mut discovery = axelar_gateway::discovery::new(ctx); - sui::test_utils::destroy(its); - sui::test_utils::destroy(discovery); - } + register_transaction(&mut its, &mut discovery); - #[test] - fun test_discovery_interchain_transfer_with_data() { - let ctx = &mut sui::tx_context::dummy(); - let mut its = its::its::new(); - let mut discovery = axelar_gateway::discovery::new(ctx); - - register_transaction(&mut its, &mut discovery); - - assert!(discovery.get_transaction(its.channel_id()) == get_initial_tx(&its), 0); - - let token_id = @0x1234; - let source_address = b"source address"; - let target_channel = @0x5678; - let amount = 1905; - let tx_data = sui::bcs::to_bytes(&get_initial_tx(&its)); - let mut writer = abi::new_writer(2); - writer - .write_bytes(tx_data) - .write_u256(1245); - let data = writer.into_bytes(); - - writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(address::to_u256(token_id)) - .write_bytes(source_address) - .write_bytes(target_channel.to_bytes()) - .write_u256(amount) - .write_bytes(data); - let payload = writer.into_bytes(); - - its.test_add_registered_coin_type(its::token_id::from_address(token_id), std::type_name::get()); - - let mut reader = abi::new_reader(payload); - reader.skip_slot(); // skip message_type - - assert!(get_call_info(&its, payload) == get_interchain_transfer_tx(&its, &mut reader), 1); - - sui::test_utils::destroy(its); - sui::test_utils::destroy(discovery); - } + assert!( + discovery.get_transaction(its.channel_id()) == get_initial_tx(&its), + 0, + ); + assert!(its.relayer_discovery_id() == object::id(&discovery), 1); - #[test] - fun test_discovery_deploy_token() { - let ctx = &mut sui::tx_context::dummy(); - let mut its = its::its::new(); - let mut discovery = axelar_gateway::discovery::new(ctx); - - register_transaction(&mut its, &mut discovery); - - let token_id = @0x1234; - let name = b"name"; - let symbol = b"symbol"; - let decimals = 15; - let distributor = @0x0325; - let mut writer = abi::new_writer(6); - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(address::to_u256(token_id)) - .write_bytes(name) - .write_bytes(symbol) - .write_u256(decimals) - .write_bytes(distributor.to_bytes()); - let payload = writer.into_bytes(); - - let type_arg = std::type_name::get(); - its.test_add_unregistered_coin_type(its::token_id::unregistered_token_id(&ascii::string(symbol), (decimals as u8)), type_arg); - let tx_block = get_call_info(&its, payload); - - let mut reader = abi::new_reader(payload); - reader.skip_slot(); // skip message_type - - assert!(tx_block == get_deploy_interchain_token_tx(&its, &mut reader), 1); - - assert!(tx_block.is_final(), 2); - let mut move_calls = tx_block.move_calls(); - assert!(move_calls.length() == 1, 3); - let call_info = move_calls.pop_back(); - assert!(call_info.function().package_id_from_function() == package_id(), 4); - assert!(call_info.function().module_name() == ascii::string(b"service"), 5); - assert!(call_info.function().name() == ascii::string(b"receive_deploy_interchain_token"), 6); - let mut arg = vector[0]; - arg.append(object::id_address(&its).to_bytes()); + sui::test_utils::destroy(its); + sui::test_utils::destroy(discovery); +} - let arguments = vector[ - arg, - vector[2] - ]; - assert!(call_info.arguments() == arguments, 7); - assert!(call_info.type_arguments() == vector[type_arg.into_string()], 8); +#[test] +fun test_discovery_interchain_transfer() { + let ctx = &mut sui::tx_context::dummy(); + let mut its = its::its::new(); + let mut discovery = axelar_gateway::discovery::new(ctx); + + register_transaction(&mut its, &mut discovery); + + let token_id = @0x1234; + let source_address = b"source address"; + let target_channel = @0x5678; + let amount = 1905; + let data = b""; + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(address::to_u256(token_id)) + .write_bytes(source_address) + .write_bytes(target_channel.to_bytes()) + .write_u256(amount) + .write_bytes(data); + let payload = writer.into_bytes(); + + let type_arg = std::type_name::get(); + its.test_add_registered_coin_type( + its::token_id::from_address(token_id), + type_arg, + ); + let tx_block = get_call_info(&its, payload); + + let mut reader = abi::new_reader(payload); + reader.skip_slot(); // skip message_type + + assert!(tx_block == get_interchain_transfer_tx(&its, &mut reader), 1); + assert!(tx_block.is_final() && tx_block.move_calls().length() == 1, 2); + + let call_info = tx_block.move_calls().pop_back(); + + assert!( + call_info.function().package_id_from_function() == package_id(), + 3, + ); + assert!(call_info.function().module_name() == ascii::string(b"service"), 4); + assert!( + call_info.function().name() == + ascii::string(b"receive_interchain_transfer"), + 5, + ); + let mut arg = vector[0]; + arg.append(object::id_address(&its).to_bytes()); + + let arguments = vector[ + arg, + vector[2], + vector[0, 6], + ]; + assert!(call_info.arguments() == arguments, 6); + assert!(call_info.type_arguments() == vector[type_arg.into_string()], 7); + + sui::test_utils::destroy(its); + sui::test_utils::destroy(discovery); +} - sui::test_utils::destroy(its); - sui::test_utils::destroy(discovery); - } +#[test] +fun test_discovery_interchain_transfer_with_data() { + let ctx = &mut sui::tx_context::dummy(); + let mut its = its::its::new(); + let mut discovery = axelar_gateway::discovery::new(ctx); + + register_transaction(&mut its, &mut discovery); + + assert!( + discovery.get_transaction(its.channel_id()) == get_initial_tx(&its), + 0, + ); + + let token_id = @0x1234; + let source_address = b"source address"; + let target_channel = @0x5678; + let amount = 1905; + let tx_data = sui::bcs::to_bytes(&get_initial_tx(&its)); + let mut writer = abi::new_writer(2); + writer.write_bytes(tx_data).write_u256(1245); + let data = writer.into_bytes(); + + writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(address::to_u256(token_id)) + .write_bytes(source_address) + .write_bytes(target_channel.to_bytes()) + .write_u256(amount) + .write_bytes(data); + let payload = writer.into_bytes(); + + its.test_add_registered_coin_type( + its::token_id::from_address(token_id), + std::type_name::get(), + ); + + let mut reader = abi::new_reader(payload); + reader.skip_slot(); // skip message_type + + assert!( + get_call_info(&its, payload) == + get_interchain_transfer_tx(&its, &mut reader), + 1, + ); + + sui::test_utils::destroy(its); + sui::test_utils::destroy(discovery); +} +#[test] +fun test_discovery_deploy_token() { + let ctx = &mut sui::tx_context::dummy(); + let mut its = its::its::new(); + let mut discovery = axelar_gateway::discovery::new(ctx); + + register_transaction(&mut its, &mut discovery); + + let token_id = @0x1234; + let name = b"name"; + let symbol = b"symbol"; + let decimals = 15; + let distributor = @0x0325; + let mut writer = abi::new_writer(6); + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(address::to_u256(token_id)) + .write_bytes(name) + .write_bytes(symbol) + .write_u256(decimals) + .write_bytes(distributor.to_bytes()); + let payload = writer.into_bytes(); + + let type_arg = std::type_name::get(); + its.test_add_unregistered_coin_type( + its::token_id::unregistered_token_id( + &ascii::string(symbol), + (decimals as u8), + ), + type_arg, + ); + let tx_block = get_call_info(&its, payload); + + let mut reader = abi::new_reader(payload); + reader.skip_slot(); // skip message_type + + assert!(tx_block == get_deploy_interchain_token_tx(&its, &mut reader), 1); + + assert!(tx_block.is_final(), 2); + let mut move_calls = tx_block.move_calls(); + assert!(move_calls.length() == 1, 3); + let call_info = move_calls.pop_back(); + assert!( + call_info.function().package_id_from_function() == package_id(), + 4, + ); + assert!(call_info.function().module_name() == ascii::string(b"service"), 5); + assert!( + call_info.function().name() == + ascii::string(b"receive_deploy_interchain_token"), + 6, + ); + let mut arg = vector[0]; + arg.append(object::id_address(&its).to_bytes()); + + let arguments = vector[ + arg, + vector[2], + ]; + assert!(call_info.arguments() == arguments, 7); + assert!(call_info.type_arguments() == vector[type_arg.into_string()], 8); + + sui::test_utils::destroy(its); + sui::test_utils::destroy(discovery); } diff --git a/move/its/sources/flow_limit.move b/move/its/sources/flow_limit.move index 1485ffe0..6e3a527f 100644 --- a/move/its/sources/flow_limit.move +++ b/move/its/sources/flow_limit.move @@ -1,52 +1,66 @@ -module its::flow_limit { - use sui::clock::Clock; +module its::flow_limit; - const EPOCH_TIME: u64 = 6 * 60 * 60 * 1000; +use sui::clock::Clock; - const EFlowLimitExceeded: u64 = 0; +const EPOCH_TIME: u64 = 6 * 60 * 60 * 1000; - public struct FlowLimit has store, copy, drop { - flow_limit: u64, - flow_in: u64, - flow_out: u64, - current_epoch: u64, - } +const EFlowLimitExceeded: u64 = 0; - public(package) fun new(): FlowLimit { - FlowLimit { - flow_limit: 0, - flow_in: 0, - flow_out: 0, - current_epoch: 0, - } - } +public struct FlowLimit has store, copy, drop { + flow_limit: u64, + flow_in: u64, + flow_out: u64, + current_epoch: u64, +} - fun update_epoch(self: &mut FlowLimit, clock: &Clock) { - let epoch = clock.timestamp_ms() / EPOCH_TIME; - if(epoch > self.current_epoch) { - self.current_epoch = epoch; - self.flow_in = 0; - self.flow_out = 0; - } +public(package) fun new(): FlowLimit { + FlowLimit { + flow_limit: 0, + flow_in: 0, + flow_out: 0, + current_epoch: 0, } +} - public(package) fun add_flow_in(self: &mut FlowLimit, amount: u64, clock: &Clock) { - if (self.flow_limit == 0) return; - - update_epoch(self, clock); - assert!(self.flow_in + amount < self.flow_limit + self.flow_out, EFlowLimitExceeded); - self.flow_in = self.flow_in + amount; +fun update_epoch(self: &mut FlowLimit, clock: &Clock) { + let epoch = clock.timestamp_ms() / EPOCH_TIME; + if (epoch > self.current_epoch) { + self.current_epoch = epoch; + self.flow_in = 0; + self.flow_out = 0; } +} - public(package) fun add_flow_out(self: &mut FlowLimit, amount: u64, clock: &Clock) { - if (self.flow_limit == 0) return; +public(package) fun add_flow_in( + self: &mut FlowLimit, + amount: u64, + clock: &Clock, +) { + if (self.flow_limit == 0) return; - update_epoch(self, clock); - assert!(self.flow_out + amount < self.flow_limit + self.flow_in, EFlowLimitExceeded); - self.flow_out = self.flow_out + amount; - } + update_epoch(self, clock); + assert!( + self.flow_in + amount < self.flow_limit + self.flow_out, + EFlowLimitExceeded, + ); + self.flow_in = self.flow_in + amount; +} - public(package) fun set_flow_limit(self: &mut FlowLimit, flow_limit: u64) { - self.flow_limit = flow_limit; - } -} \ No newline at end of file +public(package) fun add_flow_out( + self: &mut FlowLimit, + amount: u64, + clock: &Clock, +) { + if (self.flow_limit == 0) return; + + update_epoch(self, clock); + assert!( + self.flow_out + amount < self.flow_limit + self.flow_in, + EFlowLimitExceeded, + ); + self.flow_out = self.flow_out + amount; +} + +public(package) fun set_flow_limit(self: &mut FlowLimit, flow_limit: u64) { + self.flow_limit = flow_limit; +} diff --git a/move/its/sources/its.move b/move/its/sources/its.move index d07d3a12..9855acb6 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -1,257 +1,295 @@ +module its::its; + +use axelar_gateway::channel::Channel; +use axelar_gateway::discovery::RelayerDiscovery; +use its::address_tracker::{Self, InterchainAddressTracker}; +use its::coin_info::CoinInfo; +use its::coin_management::CoinManagement; +use its::token_id::{Self, TokenId, UnregisteredTokenId}; +use std::ascii::String; +use std::string; +use std::type_name::{Self, TypeName}; +use sui::bag::{Self, Bag}; +use sui::coin::{TreasuryCap, CoinMetadata}; +use sui::table::{Self, Table}; + +/// Trying to read a token that doesn't exist. +const ENotFound: u64 = 0; +const EUnregisteredCoin: u64 = 1; + +public struct ITS has key { + id: UID, + channel: Channel, + address_tracker: InterchainAddressTracker, + unregistered_coin_types: Table, + unregistered_coin_info: Bag, + registered_coin_types: Table, + registered_coins: Bag, + relayer_discovery_id: ID, +} +public struct CoinData has store { + coin_management: CoinManagement, + coin_info: CoinInfo, +} -module its::its { - use std::string; - use std::ascii::String; - use std::type_name::{Self, TypeName}; - - use sui::bag::{Self, Bag}; - use sui::table::{Self, Table}; - use sui::coin::{TreasuryCap, CoinMetadata}; - - use axelar_gateway::channel::Channel; - use axelar_gateway::discovery::RelayerDiscovery; - - use its::token_id::{Self, TokenId, UnregisteredTokenId}; - use its::address_tracker::{Self, InterchainAddressTracker}; - use its::coin_info::CoinInfo; - use its::coin_management::CoinManagement; - - /// Trying to read a token that doesn't exist. - const ENotFound: u64 = 0; - const EUnregisteredCoin: u64 = 1; - - public struct ITS has key { - id: UID, - channel: Channel, - - address_tracker: InterchainAddressTracker, - - unregistered_coin_types: Table, - unregistered_coin_info: Bag, - - registered_coin_types: Table, - registered_coins: Bag, - - relayer_discovery_id: ID, - } - - public struct CoinData has store { - coin_management: CoinManagement, - coin_info: CoinInfo, - } - - public struct UnregisteredCoinData has store { - treasury_cap: TreasuryCap, - coin_metadata: CoinMetadata, - } - - fun init(ctx: &mut TxContext) { - transfer::share_object(ITS { - id: object::new(ctx), - channel: axelar_gateway::channel::new(ctx), - - address_tracker: address_tracker::new( - ctx, - ), - - registered_coins: bag::new(ctx), - registered_coin_types: table::new(ctx), - - unregistered_coin_info: bag::new(ctx), - unregistered_coin_types: table::new(ctx), - - relayer_discovery_id: object::id_from_address(@0x0), - }); - } - - public(package) fun set_relayer_discovery_id(self: &mut ITS, relayer_discovery: &RelayerDiscovery) { - self.relayer_discovery_id = object::id(relayer_discovery); - } - - public(package) fun relayer_discovery_id(self: &ITS): ID { - self.relayer_discovery_id - } - - public(package) fun set_trusted_address(self: &mut ITS, chain_name: String, trusted_address: String) { - self.address_tracker.set_trusted_address(chain_name, trusted_address); - } +public struct UnregisteredCoinData has store { + treasury_cap: TreasuryCap, + coin_metadata: CoinMetadata, +} - public fun get_unregistered_coin_type( - self: &ITS, symbol: &String, decimals: u8 - ): &TypeName { - let key = token_id::unregistered_token_id(symbol, decimals); +fun init(ctx: &mut TxContext) { + transfer::share_object(ITS { + id: object::new(ctx), + channel: axelar_gateway::channel::new(ctx), + address_tracker: address_tracker::new( + ctx, + ), + registered_coins: bag::new(ctx), + registered_coin_types: table::new(ctx), + unregistered_coin_info: bag::new(ctx), + unregistered_coin_types: table::new(ctx), + relayer_discovery_id: object::id_from_address(@0x0), + }); +} - assert!(self.unregistered_coin_types.contains(key), ENotFound); - &self.unregistered_coin_types[key] - } +public(package) fun set_relayer_discovery_id( + self: &mut ITS, + relayer_discovery: &RelayerDiscovery, +) { + self.relayer_discovery_id = object::id(relayer_discovery); +} - public fun get_registered_coin_type(self: &ITS, token_id: TokenId): &TypeName { - assert!(self.registered_coin_types.contains(token_id), EUnregisteredCoin); - &self.registered_coin_types[token_id] - } +public(package) fun relayer_discovery_id(self: &ITS): ID { + self.relayer_discovery_id +} - public fun get_coin_data(self: &ITS, token_id: TokenId): &CoinData { - assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); - &self.registered_coins[token_id] - } +public(package) fun set_trusted_address( + self: &mut ITS, + chain_name: String, + trusted_address: String, +) { + self.address_tracker.set_trusted_address(chain_name, trusted_address); +} - public(package) fun get_coin_data_mut(self: &mut ITS, token_id: TokenId): &mut CoinData { - assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); - &mut self.registered_coins[token_id] - } +public fun get_unregistered_coin_type( + self: &ITS, + symbol: &String, + decimals: u8, +): &TypeName { + let key = token_id::unregistered_token_id(symbol, decimals); - public(package) fun get_coin_scaling(self: &CoinData): u256 { - self.coin_info.scaling() - } + assert!(self.unregistered_coin_types.contains(key), ENotFound); + &self.unregistered_coin_types[key] +} - public fun get_coin_info(self: &ITS, token_id: TokenId): &CoinInfo { - &get_coin_data(self, token_id).coin_info - } +public fun get_registered_coin_type(self: &ITS, token_id: TokenId): &TypeName { + assert!(self.registered_coin_types.contains(token_id), EUnregisteredCoin); + &self.registered_coin_types[token_id] +} - public fun token_name(self: &ITS, token_id: TokenId): string::String { - get_coin_info(self, token_id).name() - } +public fun get_coin_data(self: &ITS, token_id: TokenId): &CoinData { + assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); + &self.registered_coins[token_id] +} - public fun token_symbol(self: &ITS, token_id: TokenId): String { - get_coin_info(self, token_id).symbol() - } +public(package) fun get_coin_data_mut( + self: &mut ITS, + token_id: TokenId, +): &mut CoinData { + assert!(self.registered_coins.contains(token_id), EUnregisteredCoin); + &mut self.registered_coins[token_id] +} - public fun token_decimals(self: &ITS, token_id: TokenId): u8 { - get_coin_info(self, token_id).decimals() - } +public(package) fun get_coin_scaling(self: &CoinData): u256 { + self.coin_info.scaling() +} - public fun token_remote_decimals(self: &ITS, token_id: TokenId): u8 { - get_coin_info(self, token_id).remote_decimals() - } +public fun get_coin_info(self: &ITS, token_id: TokenId): &CoinInfo { + &get_coin_data(self, token_id).coin_info +} - public fun get_trusted_address(self: &ITS, chain_name: String): String { - *self.address_tracker.get_trusted_address(chain_name) - } +public fun token_name(self: &ITS, token_id: TokenId): string::String { + get_coin_info(self, token_id).name() +} - public fun is_trusted_address(self: &ITS, source_chain: String, source_address: String): bool { - self.address_tracker.is_trusted_address(source_chain, source_address) - } +public fun token_symbol(self: &ITS, token_id: TokenId): String { + get_coin_info(self, token_id).symbol() +} - public fun channel_id(self: &ITS): ID { - self.channel.id() - } +public fun token_decimals(self: &ITS, token_id: TokenId): u8 { + get_coin_info(self, token_id).decimals() +} - // === Friend-only === - public(package) fun channel(self: &ITS): &Channel { - &self.channel - } +public fun token_remote_decimals(self: &ITS, token_id: TokenId): u8 { + get_coin_info(self, token_id).remote_decimals() +} - public(package) fun channel_mut(self: &mut ITS): &mut Channel { - &mut self.channel - } +public fun get_trusted_address(self: &ITS, chain_name: String): String { + *self.address_tracker.get_trusted_address(chain_name) +} - public(package) fun coin_management_mut(self: &mut ITS, token_id: TokenId): &mut CoinManagement { - let coin_data: &mut CoinData = &mut self.registered_coins[token_id]; - &mut coin_data.coin_management - } +public fun is_trusted_address( + self: &ITS, + source_chain: String, + source_address: String, +): bool { + self.address_tracker.is_trusted_address(source_chain, source_address) +} - public(package) fun add_unregistered_coin( - self: &mut ITS, - token_id: UnregisteredTokenId, - treasury_cap: TreasuryCap, - coin_metadata: CoinMetadata - ) { - self.unregistered_coin_info.add(token_id, UnregisteredCoinData { - treasury_cap, - coin_metadata, - }); - - let type_name = type_name::get(); - add_unregistered_coin_type(self, token_id, type_name); - } +public fun channel_id(self: &ITS): ID { + self.channel.id() +} - public(package) fun remove_unregistered_coin( - self: &mut ITS, token_id: UnregisteredTokenId - ): (TreasuryCap, CoinMetadata) { - let UnregisteredCoinData { - treasury_cap, - coin_metadata - } = self.unregistered_coin_info.remove(token_id); +// === Friend-only === +public(package) fun channel(self: &ITS): &Channel { + &self.channel +} - remove_unregistered_coin_type(self, token_id); +public(package) fun channel_mut(self: &mut ITS): &mut Channel { + &mut self.channel +} - (treasury_cap, coin_metadata) - } +public(package) fun coin_management_mut( + self: &mut ITS, + token_id: TokenId, +): &mut CoinManagement { + let coin_data: &mut CoinData = &mut self.registered_coins[token_id]; + &mut coin_data.coin_management +} - public(package) fun add_registered_coin( - self: &mut ITS, - token_id: TokenId, - mut coin_management: CoinManagement, - coin_info: CoinInfo, - ) { - coin_management.set_scaling(coin_info.scaling()); - self.registered_coins.add(token_id, CoinData { - coin_management, - coin_info, - }); - - let type_name = type_name::get(); - add_registered_coin_type(self, token_id, type_name); - } +public(package) fun add_unregistered_coin( + self: &mut ITS, + token_id: UnregisteredTokenId, + treasury_cap: TreasuryCap, + coin_metadata: CoinMetadata, +) { + self + .unregistered_coin_info + .add( + token_id, + UnregisteredCoinData { + treasury_cap, + coin_metadata, + }, + ); + + let type_name = type_name::get(); + add_unregistered_coin_type(self, token_id, type_name); +} - // === Private === +public(package) fun remove_unregistered_coin( + self: &mut ITS, + token_id: UnregisteredTokenId, +): (TreasuryCap, CoinMetadata) { + let UnregisteredCoinData { + treasury_cap, + coin_metadata, + } = self.unregistered_coin_info.remove(token_id); - fun add_unregistered_coin_type(self: &mut ITS, token_id: UnregisteredTokenId, type_name: TypeName) { - self.unregistered_coin_types.add(token_id, type_name); - } + remove_unregistered_coin_type(self, token_id); - fun remove_unregistered_coin_type(self: &mut ITS, token_id: UnregisteredTokenId): TypeName { - self.unregistered_coin_types.remove(token_id) - } + (treasury_cap, coin_metadata) +} - fun add_registered_coin_type(self: &mut ITS, token_id: TokenId, type_name: TypeName) { - self.registered_coin_types.add(token_id, type_name); - } +public(package) fun add_registered_coin( + self: &mut ITS, + token_id: TokenId, + mut coin_management: CoinManagement, + coin_info: CoinInfo, +) { + coin_management.set_scaling(coin_info.scaling()); + self + .registered_coins + .add( + token_id, + CoinData { + coin_management, + coin_info, + }, + ); + + let type_name = type_name::get(); + add_registered_coin_type(self, token_id, type_name); +} - #[allow(unused_function)] - fun remove_registered_coin_type(self: &mut ITS, token_id: TokenId): TypeName { - self.registered_coin_types.remove(token_id) - } +// === Private === - #[test_only] - public fun new(): ITS { - let ctx = &mut sui::tx_context::dummy(); - ITS { - id: object::new(ctx), - channel: axelar_gateway::channel::new(ctx), +fun add_unregistered_coin_type( + self: &mut ITS, + token_id: UnregisteredTokenId, + type_name: TypeName, +) { + self.unregistered_coin_types.add(token_id, type_name); +} - address_tracker: address_tracker::new( - ctx, - ), +fun remove_unregistered_coin_type( + self: &mut ITS, + token_id: UnregisteredTokenId, +): TypeName { + self.unregistered_coin_types.remove(token_id) +} - registered_coins: bag::new(ctx), - registered_coin_types: table::new(ctx), +fun add_registered_coin_type( + self: &mut ITS, + token_id: TokenId, + type_name: TypeName, +) { + self.registered_coin_types.add(token_id, type_name); +} - unregistered_coin_info: bag::new(ctx), - unregistered_coin_types: table::new(ctx), +#[allow(unused_function)] +fun remove_registered_coin_type(self: &mut ITS, token_id: TokenId): TypeName { + self.registered_coin_types.remove(token_id) +} - relayer_discovery_id: object::id_from_address(@0x0) - } +#[test_only] +public fun new(): ITS { + let ctx = &mut sui::tx_context::dummy(); + ITS { + id: object::new(ctx), + channel: axelar_gateway::channel::new(ctx), + address_tracker: address_tracker::new( + ctx, + ), + registered_coins: bag::new(ctx), + registered_coin_types: table::new(ctx), + unregistered_coin_info: bag::new(ctx), + unregistered_coin_types: table::new(ctx), + relayer_discovery_id: object::id_from_address(@0x0), } +} - #[test_only] - public fun test_add_unregistered_coin_type(self: &mut ITS, token_id: UnregisteredTokenId, type_name: TypeName) { - self.add_unregistered_coin_type(token_id, type_name); - } +#[test_only] +public fun test_add_unregistered_coin_type( + self: &mut ITS, + token_id: UnregisteredTokenId, + type_name: TypeName, +) { + self.add_unregistered_coin_type(token_id, type_name); +} - #[test_only] - public fun test_remove_unregistered_coin_type(self: &mut ITS, token_id: UnregisteredTokenId): TypeName { - self.remove_unregistered_coin_type(token_id) - } +#[test_only] +public fun test_remove_unregistered_coin_type( + self: &mut ITS, + token_id: UnregisteredTokenId, +): TypeName { + self.remove_unregistered_coin_type(token_id) +} - #[test_only] - public fun test_add_registered_coin_type(self: &mut ITS, token_id: TokenId, type_name: TypeName) { - self.add_registered_coin_type(token_id, type_name); - } +#[test_only] +public fun test_add_registered_coin_type( + self: &mut ITS, + token_id: TokenId, + type_name: TypeName, +) { + self.add_registered_coin_type(token_id, type_name); +} - #[test_only] - public fun test_remove_registered_coin_type(self: &mut ITS, token_id: TokenId): TypeName { - self.remove_registered_coin_type(token_id) - } +#[test_only] +public fun test_remove_registered_coin_type( + self: &mut ITS, + token_id: TokenId, +): TypeName { + self.remove_registered_coin_type(token_id) } diff --git a/move/its/sources/service.move b/move/its/sources/service.move index ea78081a..8b5cfe69 100644 --- a/move/its/sources/service.move +++ b/move/its/sources/service.move @@ -1,292 +1,347 @@ -module its::service { - use std::string; - use std::ascii::{Self, String}; - use std::type_name; - - use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata}; - use sui::address; - use sui::event; - use sui::bcs; - use sui::clock::Clock; - - use abi::abi; - - use axelar_gateway::channel::{Self, ApprovedMessage}; - - use governance::governance::{Self, Governance}; - - use its::its::{ITS}; - use its::coin_info::{Self, CoinInfo}; - use its::token_id::{Self, TokenId}; - use its::coin_management::{Self, CoinManagement}; - use its::utils as its_utils; - - use axelar_gateway::gateway; - use axelar_gateway::channel::Channel; - - const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; - const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; - //const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; - - // address::to_u256(address::from_bytes(keccak256(b"sui-set-trusted-addresses"))); - const MESSAGE_TYPE_SET_TRUSTED_ADDRESSES: u256 = 0x2af37a0d5d48850a855b1aaaf57f726c107eb99b40eabf4cc1ba30410cfa2f68; - - const DECIMALS_CAP: u8 = 9; - - const EUntrustedAddress: u64 = 0; - const EInvalidMessageType: u64 = 1; - const EWrongDestination: u64 = 2; - const EInterchainTransferHasData: u64 = 3; - const EInterchainTransferHasNoData: u64 = 4; - const EModuleNameDoesNotMatchSymbol: u64 = 5; - const ENotDistributor: u64 = 6; - const ENonZeroTotalSupply: u64 = 7; - const EUnregisteredCoinHasUrl: u64 = 8; - const EMalformedTrustedAddresses: u64 = 9; - - public struct CoinRegistered has copy, drop { - token_id: TokenId, - } - - public fun register_coin( - self: &mut ITS, coin_info: CoinInfo, coin_management: CoinManagement - ) { - let token_id = token_id::from_coin_data(&coin_info, &coin_management); - - self.add_registered_coin(token_id, coin_management, coin_info); - - event::emit(CoinRegistered { - token_id - }) - } - - public fun deploy_remote_interchain_token( - self: &mut ITS, token_id: TokenId, destination_chain: String - ) { - let coin_info = self.get_coin_info(token_id); - let name = coin_info.name(); - let symbol = coin_info.symbol(); - let decimals = coin_info.decimals(); - let mut writer = abi::new_writer(6); - - writer - .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) - .write_u256(token_id.to_u256()) - .write_bytes(*string::bytes(&name)) - .write_bytes(*ascii::as_bytes(&symbol)) - .write_u256((decimals as u256)) - .write_bytes(vector::empty()); - - send_payload(self, destination_chain, writer.into_bytes()); - } - - public fun interchain_transfer( - self: &mut ITS, - token_id: TokenId, - coin: Coin, - destination_chain: String, - destination_address: vector, - metadata: vector, - clock: &Clock, - ctx: &mut TxContext, - ) { - let amount = self.coin_management_mut(token_id) - .take_coin(coin, clock); - let (_version, data) = its_utils::decode_metadata(metadata); - let mut writer = abi::new_writer(6); - - writer - .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) - .write_u256(token_id.to_u256()) - .write_bytes(ctx.sender().to_bytes()) - .write_bytes(destination_address) - .write_u256(amount) - .write_bytes(data); - - send_payload(self, destination_chain, writer.into_bytes()); - } - - public fun receive_interchain_transfer(self: &mut ITS, approved_message: ApprovedMessage, clock: &Clock, ctx: &mut TxContext) { - let (_, payload) = decode_approved_message(self, approved_message); - let mut reader = abi::new_reader(payload); - assert!(reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, EInvalidMessageType); - - let token_id = token_id::from_u256(reader.read_u256()); - reader.skip_slot(); // skip source_address - let destination_address = address::from_bytes(reader.read_bytes()); - let amount = reader.read_u256(); - let data = reader.read_bytes(); - - assert!(data.is_empty(), EInterchainTransferHasData); - - let coin = self - .coin_management_mut(token_id) - .give_coin(amount, clock, ctx); - - transfer::public_transfer(coin, destination_address) - } +module its::service; + +use abi::abi; +use axelar_gateway::channel::{Self, ApprovedMessage, Channel}; +use axelar_gateway::gateway; +use governance::governance::{Self, Governance}; +use its::coin_info::{Self, CoinInfo}; +use its::coin_management::{Self, CoinManagement}; +use its::its::ITS; +use its::token_id::{Self, TokenId}; +use its::utils as its_utils; +use std::ascii::{Self, String}; +use std::string; +use std::type_name; +use sui::address; +use sui::bcs; +use sui::clock::Clock; +use sui::coin::{Self, Coin, TreasuryCap, CoinMetadata}; +use sui::event; + +const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u256 = 0; +const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; +//const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u256 = 2; + +// address::to_u256(address::from_bytes(keccak256(b"sui-set-trusted-addresses"))); +const MESSAGE_TYPE_SET_TRUSTED_ADDRESSES: u256 = + 0x2af37a0d5d48850a855b1aaaf57f726c107eb99b40eabf4cc1ba30410cfa2f68; + +const DECIMALS_CAP: u8 = 9; + +const EUntrustedAddress: u64 = 0; +const EInvalidMessageType: u64 = 1; +const EWrongDestination: u64 = 2; +const EInterchainTransferHasData: u64 = 3; +const EInterchainTransferHasNoData: u64 = 4; +const EModuleNameDoesNotMatchSymbol: u64 = 5; +const ENotDistributor: u64 = 6; +const ENonZeroTotalSupply: u64 = 7; +const EUnregisteredCoinHasUrl: u64 = 8; +const EMalformedTrustedAddresses: u64 = 9; + +public struct CoinRegistered has copy, drop { + token_id: TokenId, +} - public fun receive_interchain_transfer_with_data( - self: &mut ITS, - approved_message: ApprovedMessage, - channel: &Channel, - clock: &Clock, - ctx: &mut TxContext, - ): (String, vector, vector, Coin) { - let (source_chain, payload) = decode_approved_message(self, approved_message); - let mut reader = abi::new_reader(payload); - assert!(reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, EInvalidMessageType); - - let token_id = token_id::from_u256(reader.read_u256()); - - let source_address = reader.read_bytes(); - let destination_address = reader.read_bytes(); - let amount = reader.read_u256(); - let data = reader.read_bytes(); - - assert!(address::from_bytes(destination_address) == channel.to_address(), EWrongDestination); - assert!(!data.is_empty(), EInterchainTransferHasNoData); - - let coin = self - .coin_management_mut(token_id) - .give_coin(amount, clock, ctx); - - ( - source_chain, - source_address, - data, - coin, - ) - } +public fun register_coin( + self: &mut ITS, + coin_info: CoinInfo, + coin_management: CoinManagement, +) { + let token_id = token_id::from_coin_data(&coin_info, &coin_management); - public fun receive_deploy_interchain_token(self: &mut ITS, approved_message: ApprovedMessage) { - let (_, payload) = decode_approved_message(self, approved_message); - let mut reader = abi::new_reader(payload); - assert!(reader.read_u256() == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, EInvalidMessageType); + self.add_registered_coin(token_id, coin_management, coin_info); - let token_id = token_id::from_u256(reader.read_u256()); - let name = string::utf8(reader.read_bytes()); - let symbol = ascii::string(reader.read_bytes()); - let remote_decimals = (reader.read_u256() as u8); - let distributor = address::from_bytes(reader.read_bytes()); - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP else remote_decimals; + event::emit(CoinRegistered { + token_id, + }) +} - let (treasury_cap, mut coin_metadata) = self.remove_unregistered_coin( - token_id::unregistered_token_id(&symbol, decimals) - ); +public fun deploy_remote_interchain_token( + self: &mut ITS, + token_id: TokenId, + destination_chain: String, +) { + let coin_info = self.get_coin_info(token_id); + let name = coin_info.name(); + let symbol = coin_info.symbol(); + let decimals = coin_info.decimals(); + let mut writer = abi::new_writer(6); + + writer + .write_u256(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) + .write_u256(token_id.to_u256()) + .write_bytes(*string::bytes(&name)) + .write_bytes(*ascii::as_bytes(&symbol)) + .write_u256((decimals as u256)) + .write_bytes(vector::empty()); + + send_payload(self, destination_chain, writer.into_bytes()); +} - treasury_cap.update_name(&mut coin_metadata, name); - //coin::update_symbol(&treasury_cap, &mut coin_metadata, symbol); +public fun interchain_transfer( + self: &mut ITS, + token_id: TokenId, + coin: Coin, + destination_chain: String, + destination_address: vector, + metadata: vector, + clock: &Clock, + ctx: &mut TxContext, +) { + let amount = self.coin_management_mut(token_id).take_coin(coin, clock); + let (_version, data) = its_utils::decode_metadata(metadata); + let mut writer = abi::new_writer(6); + + writer + .write_u256(MESSAGE_TYPE_INTERCHAIN_TRANSFER) + .write_u256(token_id.to_u256()) + .write_bytes(ctx.sender().to_bytes()) + .write_bytes(destination_address) + .write_u256(amount) + .write_bytes(data); + + send_payload(self, destination_chain, writer.into_bytes()); +} - let mut coin_management = coin_management::new_with_cap(treasury_cap); - let coin_info = coin_info::from_metadata(coin_metadata, remote_decimals); +public fun receive_interchain_transfer( + self: &mut ITS, + approved_message: ApprovedMessage, + clock: &Clock, + ctx: &mut TxContext, +) { + let (_, payload) = decode_approved_message(self, approved_message); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + reader.skip_slot(); // skip source_address + let destination_address = address::from_bytes(reader.read_bytes()); + let amount = reader.read_u256(); + let data = reader.read_bytes(); + + assert!(data.is_empty(), EInterchainTransferHasData); + + let coin = self + .coin_management_mut(token_id) + .give_coin(amount, clock, ctx); + + transfer::public_transfer(coin, destination_address) +} - coin_management.add_distributor(distributor); +public fun receive_interchain_transfer_with_data( + self: &mut ITS, + approved_message: ApprovedMessage, + channel: &Channel, + clock: &Clock, + ctx: &mut TxContext, +): (String, vector, vector, Coin) { + let (source_chain, payload) = decode_approved_message( + self, + approved_message, + ); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_INTERCHAIN_TRANSFER, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + + let source_address = reader.read_bytes(); + let destination_address = reader.read_bytes(); + let amount = reader.read_u256(); + let data = reader.read_bytes(); + + assert!( + address::from_bytes(destination_address) == channel.to_address(), + EWrongDestination, + ); + assert!(!data.is_empty(), EInterchainTransferHasNoData); + + let coin = self.coin_management_mut(token_id).give_coin(amount, clock, ctx); + + ( + source_chain, + source_address, + data, + coin, + ) +} - self.add_registered_coin(token_id, coin_management, coin_info); - } +public fun receive_deploy_interchain_token( + self: &mut ITS, + approved_message: ApprovedMessage, +) { + let (_, payload) = decode_approved_message(self, approved_message); + let mut reader = abi::new_reader(payload); + assert!( + reader.read_u256() == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, + EInvalidMessageType, + ); + + let token_id = token_id::from_u256(reader.read_u256()); + let name = string::utf8(reader.read_bytes()); + let symbol = ascii::string(reader.read_bytes()); + let remote_decimals = (reader.read_u256() as u8); + let distributor = address::from_bytes(reader.read_bytes()); + let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP + else remote_decimals; + + let (treasury_cap, mut coin_metadata) = self.remove_unregistered_coin( + token_id::unregistered_token_id(&symbol, decimals), + ); + + treasury_cap.update_name(&mut coin_metadata, name); + //coin::update_symbol(&treasury_cap, &mut coin_metadata, symbol); + + let mut coin_management = coin_management::new_with_cap(treasury_cap); + let coin_info = coin_info::from_metadata(coin_metadata, remote_decimals); + + coin_management.add_distributor(distributor); + + self.add_registered_coin(token_id, coin_management, coin_info); +} - // We need an coin with zero supply that has the proper decimals and typing, and no Url. - public fun give_unregistered_coin( - self: &mut ITS, treasury_cap: TreasuryCap, mut coin_metadata: CoinMetadata - ) { - assert!(treasury_cap.total_supply() == 0, ENonZeroTotalSupply); - assert!(coin::get_icon_url(&coin_metadata).is_none(), EUnregisteredCoinHasUrl); +// We need an coin with zero supply that has the proper decimals and typing, and no Url. +public fun give_unregistered_coin( + self: &mut ITS, + treasury_cap: TreasuryCap, + mut coin_metadata: CoinMetadata, +) { + assert!(treasury_cap.total_supply() == 0, ENonZeroTotalSupply); + assert!( + coin::get_icon_url(&coin_metadata).is_none(), + EUnregisteredCoinHasUrl, + ); - treasury_cap.update_description(&mut coin_metadata, string::utf8(b"")); + treasury_cap.update_description(&mut coin_metadata, string::utf8(b"")); - let decimals = coin_metadata.get_decimals(); - let symbol = coin_metadata.get_symbol(); + let decimals = coin_metadata.get_decimals(); + let symbol = coin_metadata.get_symbol(); - let module_name = type_name::get_module(&type_name::get()); - assert!(&module_name == &its_utils::get_module_from_symbol(&symbol), EModuleNameDoesNotMatchSymbol); + let module_name = type_name::get_module(&type_name::get()); + assert!( + &module_name == &its_utils::get_module_from_symbol(&symbol), + EModuleNameDoesNotMatchSymbol, + ); - let token_id = token_id::unregistered_token_id(&symbol, decimals); + let token_id = token_id::unregistered_token_id(&symbol, decimals); - self.add_unregistered_coin(token_id, treasury_cap, coin_metadata); - } + self.add_unregistered_coin(token_id, treasury_cap, coin_metadata); +} - public fun mint_as_distributor( - self: &mut ITS, - channel: &Channel, - token_id: TokenId, - to: address, - amount: u64, - ctx: &mut TxContext - ) { - let coin_management = self.coin_management_mut(token_id); - let distributor = channel.to_address(); - - assert!(coin_management.is_distributor(distributor), ENotDistributor); - - let coin = coin_management.mint(amount, ctx); - transfer::public_transfer(coin, to) - } +public fun mint_as_distributor( + self: &mut ITS, + channel: &Channel, + token_id: TokenId, + to: address, + amount: u64, + ctx: &mut TxContext, +) { + let coin_management = self.coin_management_mut(token_id); + let distributor = channel.to_address(); + + assert!(coin_management.is_distributor(distributor), ENotDistributor); + + let coin = coin_management.mint(amount, ctx); + transfer::public_transfer(coin, to) +} - public fun burn_as_distributor( - self: &mut ITS, - channel: &Channel, - token_id: TokenId, - coin: Coin - ) { - let coin_management = self.coin_management_mut(token_id); - let distributor = channel.to_address(); +public fun burn_as_distributor( + self: &mut ITS, + channel: &Channel, + token_id: TokenId, + coin: Coin, +) { + let coin_management = self.coin_management_mut(token_id); + let distributor = channel.to_address(); - assert!(coin_management.is_distributor(distributor), ENotDistributor); + assert!(coin_management.is_distributor(distributor), ENotDistributor); - coin_management.burn(coin); - } + coin_management.burn(coin); +} - // === Special Call Receiving - public fun set_trusted_addresses(its: &mut ITS, governance: &Governance, approved_message: ApprovedMessage) { - let (source_chain, _, source_address, payload) = channel::consume_approved_message( - its.channel_mut(), approved_message +// === Special Call Receiving +public fun set_trusted_addresses( + its: &mut ITS, + governance: &Governance, + approved_message: ApprovedMessage, +) { + let ( + source_chain, + _, + source_address, + payload, + ) = channel::consume_approved_message( + its.channel_mut(), + approved_message, + ); + + assert!( + governance::is_governance(governance, source_chain, source_address), + EUntrustedAddress, + ); + + let mut reader = abi::new_reader(payload); + let message_type = reader.read_u256(); + assert!( + message_type == MESSAGE_TYPE_SET_TRUSTED_ADDRESSES, + EInvalidMessageType, + ); + + let mut trusted_address_info = bcs::new(reader.read_bytes()); + + let mut chain_names = trusted_address_info.peel_vec_vec_u8(); + let mut trusted_addresses = trusted_address_info.peel_vec_vec_u8(); + + let length = chain_names.length(); + + assert!(length == trusted_addresses.length(), EMalformedTrustedAddresses); + + let mut i = 0; + while (i < length) { + its.set_trusted_address( + ascii::string(chain_names.pop_back()), + ascii::string(trusted_addresses.pop_back()), ); - - assert!(governance::is_governance(governance, source_chain, source_address), EUntrustedAddress); - - let mut reader = abi::new_reader(payload); - let message_type = reader.read_u256(); - assert!(message_type == MESSAGE_TYPE_SET_TRUSTED_ADDRESSES, EInvalidMessageType); - - let mut trusted_address_info = bcs::new(reader.read_bytes()); - - let mut chain_names = trusted_address_info.peel_vec_vec_u8(); - let mut trusted_addresses = trusted_address_info.peel_vec_vec_u8(); - - let length = chain_names.length(); - - assert!(length == trusted_addresses.length(), EMalformedTrustedAddresses); - - let mut i = 0; - while(i < length) { - its.set_trusted_address( - ascii::string(chain_names.pop_back()), - ascii::string(trusted_addresses.pop_back()), - ); - i = i + 1; - } + i = i + 1; } +} - // === Internal functions === +// === Internal functions === - /// Decode an approved call and check that the source chain is trusted. - fun decode_approved_message(self: &mut ITS, approved_message: ApprovedMessage): (String, vector) { - let ( - source_chain, - _, - source_address, - payload - ) = self.channel_mut().consume_approved_message(approved_message); +/// Decode an approved call and check that the source chain is trusted. +fun decode_approved_message( + self: &mut ITS, + approved_message: ApprovedMessage, +): (String, vector) { + let (source_chain, _, source_address, payload) = self + .channel_mut() + .consume_approved_message(approved_message); - assert!(self.is_trusted_address(source_chain, source_address), EUntrustedAddress); + assert!( + self.is_trusted_address(source_chain, source_address), + EUntrustedAddress, + ); - (source_chain, payload) - } + (source_chain, payload) +} - /// Send a payload to a destination chain. The destination chain needs to have a trusted address. - fun send_payload(self: &mut ITS, destination_chain: String, payload: vector) { - let destination_address = self.get_trusted_address(destination_chain); - gateway::call_contract(self.channel_mut(), destination_chain, destination_address, payload); - } +/// Send a payload to a destination chain. The destination chain needs to have a trusted address. +fun send_payload( + self: &mut ITS, + destination_chain: String, + payload: vector, +) { + let destination_address = self.get_trusted_address(destination_chain); + gateway::call_contract( + self.channel_mut(), + destination_chain, + destination_address, + payload, + ); } diff --git a/move/its/sources/token_id.move b/move/its/sources/token_id.move index b8d21361..1e02a0da 100644 --- a/move/its/sources/token_id.move +++ b/move/its/sources/token_id.move @@ -1,97 +1,102 @@ +module its::token_id; + +use its::coin_info::CoinInfo; +use its::coin_management::CoinManagement; +use std::ascii; +use std::string::String; +use std::type_name; +use sui::address; +use sui::bcs; +use sui::hash::keccak256; + +// address::to_u256(address::from_bytes(keccak256(&bcs::to_bytes>(&b"prefix-sui-token-id")))); +const PREFIX_SUI_TOKEN_ID: u256 = + 0x72efd4f4a47bdb9957673d9d0fabc22cad1544bc247ac18367ac54985919bfa3; + +public struct TokenId has store, copy, drop { + id: address, +} +public struct UnregisteredTokenId has store, copy, drop { + id: address, +} -module its::token_id { - use std::ascii; - use std::string::String; - use std::type_name; - - use sui::hash::keccak256; - use sui::address; - use sui::bcs; - - use its::coin_info::CoinInfo; - use its::coin_management::CoinManagement; - - // address::to_u256(address::from_bytes(keccak256(&bcs::to_bytes>(&b"prefix-sui-token-id")))); - const PREFIX_SUI_TOKEN_ID: u256 = 0x72efd4f4a47bdb9957673d9d0fabc22cad1544bc247ac18367ac54985919bfa3; - - public struct TokenId has store, copy, drop { - id: address - } - - public struct UnregisteredTokenId has store, copy, drop { - id: address - } - - public fun from_address(id: address): TokenId { - TokenId { id } - } - public fun from_u256(id: u256): TokenId { - TokenId { id: address::from_u256(id) } - } - - public fun to_u256(token_id: &TokenId): u256 { - address::to_u256(token_id.id) - } - - public fun from_info( - name: &String, - symbol: &ascii::String, - decimals: &u8, - has_metadata: &bool, - has_treasury: &bool - ): TokenId { - let mut vec = address::from_u256(PREFIX_SUI_TOKEN_ID).to_bytes(); - vec.append(bcs::to_bytes(&type_name::get())); - vec.append(bcs::to_bytes(name)); - vec.append(bcs::to_bytes(symbol)); - vec.append(bcs::to_bytes(decimals)); - vec.append(bcs::to_bytes(has_metadata)); - vec.append(bcs::to_bytes(has_treasury)); - TokenId { id: address::from_bytes(keccak256(&vec)) } - } +public fun from_address(id: address): TokenId { + TokenId { id } +} - public(package) fun from_coin_data( - coin_info: &CoinInfo, coin_management: &CoinManagement - ): TokenId { - from_info( - &coin_info.name(), - &coin_info.symbol(), - &coin_info.decimals(), - &option::is_some(coin_info.metadata()), - &coin_management.has_capability(), - ) - } +public fun from_u256(id: u256): TokenId { + TokenId { id: address::from_u256(id) } +} - public fun unregistered_token_id( - symbol: &ascii::String, decimals: u8 - ): UnregisteredTokenId { - let mut v = vector[decimals]; - v.append(*ascii::as_bytes(symbol)); - let id = address::from_bytes(keccak256(&v)); - UnregisteredTokenId { id } - } +public fun to_u256(token_id: &TokenId): u256 { + address::to_u256(token_id.id) +} - #[test] - fun test() { - use std::string; - use its::coin_info; +public fun from_info( + name: &String, + symbol: &ascii::String, + decimals: &u8, + has_metadata: &bool, + has_treasury: &bool, +): TokenId { + let mut vec = address::from_u256(PREFIX_SUI_TOKEN_ID).to_bytes(); + vec.append(bcs::to_bytes(&type_name::get())); + vec.append(bcs::to_bytes(name)); + vec.append(bcs::to_bytes(symbol)); + vec.append(bcs::to_bytes(decimals)); + vec.append(bcs::to_bytes(has_metadata)); + vec.append(bcs::to_bytes(has_treasury)); + TokenId { id: address::from_bytes(keccak256(&vec)) } +} - let prefix = address::to_u256( - address::from_bytes( - keccak256(&bcs::to_bytes>(&b"prefix-sui-token-id")) - ) - ); - assert!(prefix == PREFIX_SUI_TOKEN_ID, 5); +public(package) fun from_coin_data( + coin_info: &CoinInfo, + coin_management: &CoinManagement, +): TokenId { + from_info( + &coin_info.name(), + &coin_info.symbol(), + &coin_info.decimals(), + &option::is_some(coin_info.metadata()), + &coin_management.has_capability(), + ) +} - let name = string::utf8(b"Name"); - let symbol = ascii::string(b"Symbol"); - let decimals: u8 = 9; - let remote_decimals: u8 = 18; - let coin_info = coin_info::from_info(name, symbol, decimals, remote_decimals); - let mut vec = address::from_u256(PREFIX_SUI_TOKEN_ID).to_bytes(); +public fun unregistered_token_id( + symbol: &ascii::String, + decimals: u8, +): UnregisteredTokenId { + let mut v = vector[decimals]; + v.append(*ascii::as_bytes(symbol)); + let id = address::from_bytes(keccak256(&v)); + UnregisteredTokenId { id } +} - vec.append(bcs::to_bytes>(&coin_info)); - coin_info.drop(); - } +#[test] +fun test() { + use std::string; + use its::coin_info; + + let prefix = address::to_u256( + address::from_bytes( + keccak256(&bcs::to_bytes>(&b"prefix-sui-token-id")), + ), + ); + assert!(prefix == PREFIX_SUI_TOKEN_ID, 5); + + let name = string::utf8(b"Name"); + let symbol = ascii::string(b"Symbol"); + let decimals: u8 = 9; + let remote_decimals: u8 = 18; + let coin_info = coin_info::from_info( + name, + symbol, + decimals, + remote_decimals, + ); + let mut vec = address::from_u256(PREFIX_SUI_TOKEN_ID).to_bytes(); + + vec.append(bcs::to_bytes>(&coin_info)); + coin_info.drop(); } diff --git a/move/its/sources/utils.move b/move/its/sources/utils.move index 2dd17a7d..5b6de32d 100644 --- a/move/its/sources/utils.move +++ b/move/its/sources/utils.move @@ -1,110 +1,116 @@ +module its::utils; + +use std::ascii; +use sui::address; +use sui::hash::keccak256; + +const LOWERCASE_START: u8 = 97; +const UPPERCASE_START: u8 = 65; +const NUMBERS_START: u8 = 48; +const SPACE: u8 = 32; +const UNDERSCORE: u8 = 95; +const ALPHABET_LENGTH: u8 = 26; +const NUMBERS_LENGTH: u8 = 10; + +entry fun is_lowercase(c: u8): bool { + c >= LOWERCASE_START && c < LOWERCASE_START + ALPHABET_LENGTH +} -module its::utils { - use std::ascii; - - use sui::hash::keccak256; - use sui::address; - - const LOWERCASE_START: u8 = 97; - const UPPERCASE_START: u8 = 65; - const NUMBERS_START: u8 = 48; - const SPACE: u8 = 32; - const UNDERSCORE: u8 = 95; - const ALPHABET_LENGTH: u8 = 26; - const NUMBERS_LENGTH: u8 = 10; - - entry fun is_lowercase(c: u8): bool { - c >= LOWERCASE_START && c < LOWERCASE_START + ALPHABET_LENGTH - } +entry fun is_uppercase(c: u8): bool { + c >= UPPERCASE_START && c < UPPERCASE_START + ALPHABET_LENGTH +} - entry fun is_uppercase(c: u8): bool { - c >= UPPERCASE_START && c < UPPERCASE_START + ALPHABET_LENGTH - } +entry fun is_number(c: u8): bool { + c >= NUMBERS_START && c < NUMBERS_START + NUMBERS_LENGTH +} - entry fun is_number(c: u8): bool { - c >= NUMBERS_START && c < NUMBERS_START + NUMBERS_LENGTH - } +public(package) fun get_module_from_symbol( + symbol: &ascii::String, +): ascii::String { + let symbolBytes = ascii::as_bytes(symbol); + let mut moduleName = vector[]; + + let (mut i, length) = (0, vector::length(symbolBytes)); + while (is_number(*vector::borrow(symbolBytes, i))) { + i = i + 1; + }; + while (i < length) { + let b = *vector::borrow(symbolBytes, i); + if (is_lowercase(b) || is_number(b)) { + moduleName.push_back(b); + } else if (is_uppercase(b)) { + moduleName.push_back(b - UPPERCASE_START + LOWERCASE_START); + } else if (b == UNDERSCORE || b == SPACE) { + moduleName.push_back(UNDERSCORE); + }; - public(package) fun get_module_from_symbol(symbol: &ascii::String): ascii::String { - let symbolBytes = ascii::as_bytes(symbol); - let mut moduleName = vector[]; + i = i + 1; + }; + ascii::string(moduleName) +} - let (mut i, length) = (0, vector::length(symbolBytes)); - while (is_number(*vector::borrow(symbolBytes, i))) { - i = i + 1; - }; - while (i < length) { - let b = *vector::borrow(symbolBytes, i); - if (is_lowercase(b) || is_number(b) ) { - moduleName.push_back(b); - } else if (is_uppercase(b) ) { - moduleName.push_back(b - UPPERCASE_START + LOWERCASE_START); - } else if (b == UNDERSCORE || b == SPACE) { - moduleName.push_back(UNDERSCORE); - }; +public(package) fun hash_coin_info( + symbol: &ascii::String, + decimals: &u8, +): address { + let mut v = vector[*decimals]; + v.append(*symbol.as_bytes()); + address::from_bytes(keccak256(&v)) +} +public(package) fun decode_metadata( + mut metadata: vector, +): (u32, vector) { + if (metadata.length() < 4) { + (0, vector[]) + } else { + let mut i = 0; + let mut version: u32 = 0; + while (i < 4) { + version = + (version << (8 as u8) as u32) + (metadata.remove(0) as u32); i = i + 1; }; - ascii::string(moduleName) - } - public(package) fun hash_coin_info(symbol: &ascii::String, decimals: &u8): address { - let mut v = vector[*decimals]; - v.append(*symbol.as_bytes()); - address::from_bytes(keccak256(&v)) + (version, metadata) } +} - public(package) fun decode_metadata(mut metadata: vector): (u32, vector) { - if (metadata.length() < 4) { - (0, vector[]) +public(package) fun pow(mut base: u256, mut exponent: u8): u256 { + let mut res: u256 = 1; + while (exponent > 0) { + if (exponent % 2 == 0) { + base = base * base; + exponent = exponent / 2; } else { - let mut i = 0; - let mut version: u32 = 0; - while (i < 4) { - version = (version << (8 as u8) as u32) + (metadata.remove(0) as u32); - i = i + 1; - }; - - (version, metadata) + res = res * base; + exponent = exponent - 1; } - } + }; + res +} - public(package) fun pow(mut base: u256, mut exponent: u8): u256 { - let mut res: u256 = 1; - while (exponent > 0) { - if (exponent % 2 == 0) { - base = base * base; - exponent = exponent / 2; - } else { - res = res * base; - exponent = exponent - 1; - } - }; - res - } - - #[test] - fun test_get_module_from_symbol() { - let symbol = ascii::string(b"1(TheCool1234Coin) _ []!rdt"); - std::debug::print(&get_module_from_symbol(&symbol)); - } +#[test] +fun test_get_module_from_symbol() { + let symbol = ascii::string(b"1(TheCool1234Coin) _ []!rdt"); + std::debug::print(&get_module_from_symbol(&symbol)); +} - #[test] - fun test_decode_metadata() { - let (version, metadata) = decode_metadata(x""); - assert!(version == 0, 0); - assert!(metadata == x"", 1); +#[test] +fun test_decode_metadata() { + let (version, metadata) = decode_metadata(x""); + assert!(version == 0, 0); + assert!(metadata == x"", 1); - let (version, metadata) = decode_metadata(x"012345"); - assert!(version == 0, 3); - assert!(metadata == x"", 4); + let (version, metadata) = decode_metadata(x"012345"); + assert!(version == 0, 3); + assert!(metadata == x"", 4); - let (version, metadata) = decode_metadata(x"00000004"); - assert!(version == 4, 5); - assert!(metadata == x"", 6); + let (version, metadata) = decode_metadata(x"00000004"); + assert!(version == 4, 5); + assert!(metadata == x"", 6); - let (version, metadata) = decode_metadata(x"000000071ab768cf"); - assert!(version == 7, 7); - assert!(metadata == x"1ab768cf", 8); - } + let (version, metadata) = decode_metadata(x"000000071ab768cf"); + assert!(version == 7, 7); + assert!(metadata == x"1ab768cf", 8); } diff --git a/move/its/tests/coin_init_test.move b/move/its/tests/coin_init_test.move index 306648d1..9f0c069d 100644 --- a/move/its/tests/coin_init_test.move +++ b/move/its/tests/coin_init_test.move @@ -1,31 +1,29 @@ #[test_only] -module its::thecool1234coin___ { - use sui::coin; - use sui::url::{Url}; +module its::thecool1234coin___; - public struct THECOOL1234COIN___ has drop{ +use sui::coin; +use sui::url::Url; - } +public struct THECOOL1234COIN___ has drop {} - fun init(witness: THECOOL1234COIN___, ctx: &mut TxContext) { - let (treasury, metadata) = coin::create_currency( - witness, - 6, - b"THECOOL1234COIN___", - b"", - b"", - option::none(), - ctx - ); - transfer::public_transfer(treasury, tx_context::sender(ctx)); - transfer::public_transfer(metadata, tx_context::sender(ctx)); - } +fun init(witness: THECOOL1234COIN___, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"THECOOL1234COIN___", + b"", + b"", + option::none(), + ctx, + ); + transfer::public_transfer(treasury, tx_context::sender(ctx)); + transfer::public_transfer(metadata, tx_context::sender(ctx)); +} - #[test] - fun test_init() { - // use sui::test_scenario::{Self as ts, ctx}; - use sui::tx_context::dummy; +#[test] +fun test_init() { + // use sui::test_scenario::{Self as ts, ctx}; + use sui::tx_context::dummy; - init(THECOOL1234COIN___{}, &mut dummy()); - } + init(THECOOL1234COIN___ {}, &mut dummy()); } diff --git a/move/operators/sources/operators.move b/move/operators/sources/operators.move index 814927ed..0d2752f4 100644 --- a/move/operators/sources/operators.move +++ b/move/operators/sources/operators.move @@ -1,377 +1,417 @@ -module operators::operators { - use sui::bag::{Self, Bag}; - use sui::vec_set::{Self, VecSet}; - use sui::event; - use sui::borrow::{Self, Borrow}; - use std::ascii::String; - use std::type_name; - - // ----- - // Types - // ----- - - /// The `OwnerCap` capability representing the owner of the contract. - public struct OwnerCap has key, store { - id: UID, - } - - /// The `OperatorCap` capability representing an approved operator. - public struct OperatorCap has key, store { - id: UID, - } - - /// The main `Operators` struct storing the capabilities and operator IDs. - public struct Operators has key { - id: UID, - // The number of operators are small in practice, and under the Sui object size limit, so a dynamic collection doesn't need to be used - operators: VecSet
, - // map-like collection of capabilities stored as Sui objects - caps: Bag, - // map-like collection of Referents storing loaned capabilities. Referents only get stored for the duration of the tx. - loaned_caps: Bag, - } - - // ------ - // Errors - // ------ - - /// When the operator is not found in the set of approved operators. - const EOperatorNotFound: u64 = 0; - - /// When the capability is not found. - const ECapNotFound: u64 = 1; - - // ------ - // Events - // ------ - - /// Event emitted when a new operator is added. - public struct OperatorAdded has copy, drop { - operator: address, - } - - /// Event emitted when an operator is removed. - public struct OperatorRemoved has copy, drop { - operator: address, - } - - /// Event emitted when a capability is stored. - public struct CapabilityStored has copy, drop { - cap_id: ID, - cap_name: String, - } - - /// Event emitted when a capability is removed. - public struct CapabilityRemoved has copy, drop { - cap_id: ID, - cap_name: String, - } - - // ----- - // Setup - // ----- - - /// Initializes the contract and transfers the `OwnerCap` to the sender. - fun init(ctx: &mut TxContext) { - transfer::share_object(Operators { - id: object::new(ctx), - operators: vec_set::empty(), - caps: bag::new(ctx), - loaned_caps: bag::new(ctx), - }); - - let cap = OwnerCap { - id: object::new(ctx), - }; +module operators::operators; + +use std::ascii::String; +use std::type_name; +use sui::bag::{Self, Bag}; +use sui::borrow::{Self, Borrow}; +use sui::event; +use sui::vec_set::{Self, VecSet}; + +// ----- +// Types +// ----- + +/// The `OwnerCap` capability representing the owner of the contract. +public struct OwnerCap has key, store { + id: UID, +} - transfer::transfer(cap, ctx.sender()); - } +/// The `OperatorCap` capability representing an approved operator. +public struct OperatorCap has key, store { + id: UID, +} - // ---------------- - // Public Functions - // ---------------- +/// The main `Operators` struct storing the capabilities and operator IDs. +public struct Operators has key { + id: UID, + // The number of operators are small in practice, and under the Sui object size limit, so a dynamic collection doesn't need to be used + operators: VecSet
, + // map-like collection of capabilities stored as Sui objects + caps: Bag, + // map-like collection of Referents storing loaned capabilities. Referents only get stored for the duration of the tx. + loaned_caps: Bag, +} - /// Adds a new operator by issuing an `OperatorCap` and storing its ID. - public fun add_operator(self: &mut Operators, _: &OwnerCap, new_operator: address, ctx: &mut TxContext) { - let operator_cap = OperatorCap { - id: object::new(ctx), - }; +// ------ +// Errors +// ------ - transfer::transfer(operator_cap, new_operator); - self.operators.insert(new_operator); +/// When the operator is not found in the set of approved operators. +const EOperatorNotFound: u64 = 0; - event::emit(OperatorAdded { - operator: new_operator, - }); - } +/// When the capability is not found. +const ECapNotFound: u64 = 1; - /// Removes an operator by ID, revoking their `OperatorCap`. - public fun remove_operator(self: &mut Operators, _: &OwnerCap, operator: address) { - self.operators.remove(&operator); +// ------ +// Events +// ------ - event::emit(OperatorRemoved { - operator, - }); - } - - /// Stores a capability in the `Operators` struct. - public fun store_cap(self: &mut Operators, _: &OwnerCap, cap: T) { - let cap_id = object::id(&cap); - self.caps.add(cap_id, cap); - - event::emit(CapabilityStored { - cap_id, - cap_name: type_name::get().into_string(), - }); - } +/// Event emitted when a new operator is added. +public struct OperatorAdded has copy, drop { + operator: address, +} - /// Allows an approved operator to temporarily loan out a capability by its ID. - /// The loaned capability must be restored by the end of the transaction. - public fun loan_cap( - self: &mut Operators, - _operator_cap: &OperatorCap, - cap_id: ID, - ctx: &mut TxContext - ): (T, Borrow) { - assert!(self.operators.contains(&ctx.sender()), EOperatorNotFound); - assert!(self.caps.contains(cap_id), ECapNotFound); +/// Event emitted when an operator is removed. +public struct OperatorRemoved has copy, drop { + operator: address, +} - // Remove the capability from the `Operators` struct to loan it out - let cap = self.caps.remove(cap_id); +/// Event emitted when a capability is stored. +public struct CapabilityStored has copy, drop { + cap_id: ID, + cap_name: String, +} - // Create a new `Referent` to store the loaned capability - let mut referent = borrow::new(cap, ctx); +/// Event emitted when a capability is removed. +public struct CapabilityRemoved has copy, drop { + cap_id: ID, + cap_name: String, +} - // Create a `Borrow` hot potato object from the `Referent` that needs to be returned within the same tx - let (loaned_cap, borrow_obj) = borrow::borrow(&mut referent); +// ----- +// Setup +// ----- - // Store the `Referent` in the `Operators` struct - self.loaned_caps.add(cap_id, referent); +/// Initializes the contract and transfers the `OwnerCap` to the sender. +fun init(ctx: &mut TxContext) { + transfer::share_object(Operators { + id: object::new(ctx), + operators: vec_set::empty(), + caps: bag::new(ctx), + loaned_caps: bag::new(ctx), + }); - // Return a tuple of the borrowed capability and the Borrow hot potato object - (loaned_cap, borrow_obj) - } + let cap = OwnerCap { + id: object::new(ctx), + }; - /// Restores a previously loaned capability back to the `Operators` struct. - /// This function must be called before the end of the transaction to return the loaned capability. - public fun restore_cap( - self: &mut Operators, - _operator_cap: &OperatorCap, - cap_id: ID, - loaned_cap: T, - borrow_obj: Borrow - ) { - assert!(self.loaned_caps.contains(cap_id), ECapNotFound); + transfer::transfer(cap, ctx.sender()); +} - // Remove the `Referent` from the `Operators` struct - let mut referent = self.loaned_caps.remove(cap_id); +// ---------------- +// Public Functions +// ---------------- + +/// Adds a new operator by issuing an `OperatorCap` and storing its ID. +public fun add_operator( + self: &mut Operators, + _: &OwnerCap, + new_operator: address, + ctx: &mut TxContext, +) { + let operator_cap = OperatorCap { + id: object::new(ctx), + }; + + transfer::transfer(operator_cap, new_operator); + self.operators.insert(new_operator); + + event::emit(OperatorAdded { + operator: new_operator, + }); +} - // Put back the borrowed capability and `T` capability into the `Referent` - borrow::put_back(&mut referent, loaned_cap, borrow_obj); +/// Removes an operator by ID, revoking their `OperatorCap`. +public fun remove_operator( + self: &mut Operators, + _: &OwnerCap, + operator: address, +) { + self.operators.remove(&operator); + + event::emit(OperatorRemoved { + operator, + }); +} - // Unpack the `Referent` struct and get the `T` capability - let cap: T = borrow::destroy(referent); +/// Stores a capability in the `Operators` struct. +public fun store_cap( + self: &mut Operators, + _: &OwnerCap, + cap: T, +) { + let cap_id = object::id(&cap); + self.caps.add(cap_id, cap); + + event::emit(CapabilityStored { + cap_id, + cap_name: type_name::get().into_string(), + }); +} - // Add the capability back to the `Operators` struct - self.caps.add(cap_id, cap); - } +/// Allows an approved operator to temporarily loan out a capability by its ID. +/// The loaned capability must be restored by the end of the transaction. +public fun loan_cap( + self: &mut Operators, + _operator_cap: &OperatorCap, + cap_id: ID, + ctx: &mut TxContext, +): (T, Borrow) { + assert!(self.operators.contains(&ctx.sender()), EOperatorNotFound); + assert!(self.caps.contains(cap_id), ECapNotFound); - /// Removes a capability from the `Operators` struct. - public fun remove_cap(self: &mut Operators, _: &OwnerCap, cap_id: ID): T { - event::emit(CapabilityRemoved { - cap_id, - cap_name: type_name::get().into_string(), - }); + // Remove the capability from the `Operators` struct to loan it out + let cap = self.caps.remove(cap_id); - self.caps.remove(cap_id) - } + // Create a new `Referent` to store the loaned capability + let mut referent = borrow::new(cap, ctx); - // ----- - // Tests - // ----- - - #[test_only] - fun new_operators(ctx: &mut TxContext): Operators { - Operators { - id: object::new(ctx), - operators: vec_set::empty(), - caps: bag::new(ctx), - loaned_caps: bag::new(ctx), - } - } + // Create a `Borrow` hot potato object from the `Referent` that needs to be returned within the same tx + let (loaned_cap, borrow_obj) = borrow::borrow(&mut referent); - #[test_only] - fun destroy_operators(operators: Operators) { - let Operators { id, operators, caps, loaned_caps } = operators; + // Store the `Referent` in the `Operators` struct + self.loaned_caps.add(cap_id, referent); - id.delete(); - caps.destroy_empty(); - loaned_caps.destroy_empty(); + // Return a tuple of the borrowed capability and the Borrow hot potato object + (loaned_cap, borrow_obj) +} - let mut keys = operators.into_keys(); +/// Restores a previously loaned capability back to the `Operators` struct. +/// This function must be called before the end of the transaction to return the loaned capability. +public fun restore_cap( + self: &mut Operators, + _operator_cap: &OperatorCap, + cap_id: ID, + loaned_cap: T, + borrow_obj: Borrow, +) { + assert!(self.loaned_caps.contains(cap_id), ECapNotFound); - while (!keys.is_empty()) { - keys.pop_back(); - }; + // Remove the `Referent` from the `Operators` struct + let mut referent = self.loaned_caps.remove(cap_id); - keys.destroy_empty(); - } + // Put back the borrowed capability and `T` capability into the `Referent` + borrow::put_back(&mut referent, loaned_cap, borrow_obj); - #[test_only] - fun new_owner_cap(ctx: &mut TxContext): OwnerCap { - OwnerCap { - id: object::new(ctx), - } - } - - #[test_only] - fun destroy_owner_cap(owner_cap: OwnerCap) { - let OwnerCap { id } = owner_cap; - object::delete(id); - } + // Unpack the `Referent` struct and get the `T` capability + let cap: T = borrow::destroy(referent); - #[test_only] - fun new_operator_cap(self: &mut Operators, ctx: &mut TxContext): OperatorCap { - let operator_cap = OperatorCap { - id: object::new(ctx), - }; + // Add the capability back to the `Operators` struct + self.caps.add(cap_id, cap); +} - self.operators.insert(ctx.sender()); - operator_cap - } +/// Removes a capability from the `Operators` struct. +public fun remove_cap( + self: &mut Operators, + _: &OwnerCap, + cap_id: ID, +): T { + event::emit(CapabilityRemoved { + cap_id, + cap_name: type_name::get().into_string(), + }); + + self.caps.remove(cap_id) +} - #[test_only] - fun destroy_operator_cap(operator_cap: OperatorCap) { - let OperatorCap { id } = operator_cap; - object::delete(id); +// ----- +// Tests +// ----- + +#[test_only] +fun new_operators(ctx: &mut TxContext): Operators { + Operators { + id: object::new(ctx), + operators: vec_set::empty(), + caps: bag::new(ctx), + loaned_caps: bag::new(ctx), } +} - #[test] - fun test_init() { - let ctx = &mut tx_context::dummy(); - init(ctx); +#[test_only] +fun destroy_operators(operators: Operators) { + let Operators { id, operators, caps, loaned_caps } = operators; - let owner_cap = new_owner_cap(ctx); - destroy_owner_cap(owner_cap); - } + id.delete(); + caps.destroy_empty(); + loaned_caps.destroy_empty(); - #[test] - fun test_add_and_remove_operator() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); + let mut keys = operators.into_keys(); - let new_operator = @0x1; - add_operator(&mut operators, &owner_cap, new_operator, ctx); - assert!(operators.operators.size() == 1, 0); + while (!keys.is_empty()) { + keys.pop_back(); + }; - let operator_id = operators.operators.keys()[0]; - remove_operator(&mut operators, &owner_cap, operator_id); - assert!(operators.operators.is_empty(), 1); + keys.destroy_empty(); +} - destroy_owner_cap(owner_cap); - destroy_operators(operators); +#[test_only] +fun new_owner_cap(ctx: &mut TxContext): OwnerCap { + OwnerCap { + id: object::new(ctx), } +} - #[test] - fun test_store_and_remove_cap() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); - let external_cap = new_owner_cap(ctx); - - let external_id = object::id(&external_cap); - - store_cap(&mut operators, &owner_cap, external_cap); - assert!(operators.caps.contains(external_id), 0); - - let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, external_id, ctx); - assert!(operators.loaned_caps.contains(external_id), 1); - assert!(!operators.caps.contains(external_id), 2); - restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); - assert!(!operators.loaned_caps.contains(external_id), 3); - assert!(operators.caps.contains(external_id), 2); - - let removed_cap = remove_cap(&mut operators, &owner_cap, external_id); - assert!(!operators.caps.contains(external_id), 3); - - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_owner_cap(removed_cap); - destroy_operators(operators); - } +#[test_only] +fun destroy_owner_cap(owner_cap: OwnerCap) { + let OwnerCap { id } = owner_cap; + object::delete(id); +} - #[test] - #[expected_failure(abort_code = vec_set::EKeyDoesNotExist)] - fun test_remove_operator_fail() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); +#[test_only] +fun new_operator_cap(self: &mut Operators, ctx: &mut TxContext): OperatorCap { + let operator_cap = OperatorCap { + id: object::new(ctx), + }; - remove_operator(&mut operators, &owner_cap, ctx.sender()); + self.operators.insert(ctx.sender()); + operator_cap +} - destroy_owner_cap(owner_cap); - destroy_operators(operators); - } +#[test_only] +fun destroy_operator_cap(operator_cap: OperatorCap) { + let OperatorCap { id } = operator_cap; + object::delete(id); +} - #[test] - #[expected_failure(abort_code = EOperatorNotFound)] - fun test_borrow_cap_not_operator() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); - let external_cap = new_owner_cap(ctx); +#[test] +fun test_init() { + let ctx = &mut tx_context::dummy(); + init(ctx); - let external_id = object::id(&external_cap); + let owner_cap = new_owner_cap(ctx); + destroy_owner_cap(owner_cap); +} - store_cap(&mut operators, &owner_cap, external_cap); - remove_operator(&mut operators, &owner_cap, ctx.sender()); +#[test] +fun test_add_and_remove_operator() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); - let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, external_id, ctx); - restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); + let new_operator = @0x1; + add_operator(&mut operators, &owner_cap, new_operator, ctx); + assert!(operators.operators.size() == 1, 0); - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_operators(operators); - } + let operator_id = operators.operators.keys()[0]; + remove_operator(&mut operators, &owner_cap, operator_id); + assert!(operators.operators.is_empty(), 1); - #[test] - #[expected_failure(abort_code = ECapNotFound)] - fun test_borrow_cap_no_such_cap() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); + destroy_owner_cap(owner_cap); + destroy_operators(operators); +} - let operator_id = object::id(&operator_cap); +#[test] +fun test_store_and_remove_cap() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); + let operator_cap = new_operator_cap(&mut operators, ctx); + let external_cap = new_owner_cap(ctx); + + let external_id = object::id(&external_cap); + + store_cap(&mut operators, &owner_cap, external_cap); + assert!(operators.caps.contains(external_id), 0); + + let (cap, loaned_cap) = loan_cap( + &mut operators, + &operator_cap, + external_id, + ctx, + ); + assert!(operators.loaned_caps.contains(external_id), 1); + assert!(!operators.caps.contains(external_id), 2); + restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); + assert!(!operators.loaned_caps.contains(external_id), 3); + assert!(operators.caps.contains(external_id), 2); + + let removed_cap = remove_cap( + &mut operators, + &owner_cap, + external_id, + ); + assert!(!operators.caps.contains(external_id), 3); + + destroy_operator_cap(operator_cap); + destroy_owner_cap(owner_cap); + destroy_owner_cap(removed_cap); + destroy_operators(operators); +} - let (cap, loaned_cap) = loan_cap(&mut operators, &operator_cap, operator_id, ctx); - restore_cap(&mut operators, &operator_cap, operator_id, cap, loaned_cap); +#[test] +#[expected_failure(abort_code = vec_set::EKeyDoesNotExist)] +fun test_remove_operator_fail() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_operators(operators); - } + remove_operator(&mut operators, &owner_cap, ctx.sender()); - #[test] - #[expected_failure(abort_code = sui::dynamic_field::EFieldDoesNotExist)] - fun test_remove_cap_fail() { - let ctx = &mut tx_context::dummy(); - let mut operators = new_operators(ctx); - let owner_cap = new_owner_cap(ctx); - let operator_cap = new_operator_cap(&mut operators, ctx); - let external_cap = new_owner_cap(ctx); + destroy_owner_cap(owner_cap); + destroy_operators(operators); +} - let external_id = object::id(&external_cap); +#[test] +#[expected_failure(abort_code = EOperatorNotFound)] +fun test_borrow_cap_not_operator() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); + let operator_cap = new_operator_cap(&mut operators, ctx); + let external_cap = new_owner_cap(ctx); + + let external_id = object::id(&external_cap); + + store_cap(&mut operators, &owner_cap, external_cap); + remove_operator(&mut operators, &owner_cap, ctx.sender()); + + let (cap, loaned_cap) = loan_cap( + &mut operators, + &operator_cap, + external_id, + ctx, + ); + restore_cap(&mut operators, &operator_cap, external_id, cap, loaned_cap); + + destroy_operator_cap(operator_cap); + destroy_owner_cap(owner_cap); + destroy_operators(operators); +} - let removed_cap = remove_cap(&mut operators, &owner_cap, external_id); +#[test] +#[expected_failure(abort_code = ECapNotFound)] +fun test_borrow_cap_no_such_cap() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); + let operator_cap = new_operator_cap(&mut operators, ctx); + + let operator_id = object::id(&operator_cap); + + let (cap, loaned_cap) = loan_cap( + &mut operators, + &operator_cap, + operator_id, + ctx, + ); + restore_cap(&mut operators, &operator_cap, operator_id, cap, loaned_cap); + + destroy_operator_cap(operator_cap); + destroy_owner_cap(owner_cap); + destroy_operators(operators); +} - destroy_operator_cap(operator_cap); - destroy_owner_cap(owner_cap); - destroy_owner_cap(external_cap); - destroy_owner_cap(removed_cap); - destroy_operators(operators); - } +#[test] +#[expected_failure(abort_code = sui::dynamic_field::EFieldDoesNotExist)] +fun test_remove_cap_fail() { + let ctx = &mut tx_context::dummy(); + let mut operators = new_operators(ctx); + let owner_cap = new_owner_cap(ctx); + let operator_cap = new_operator_cap(&mut operators, ctx); + let external_cap = new_owner_cap(ctx); + + let external_id = object::id(&external_cap); + + let removed_cap = remove_cap( + &mut operators, + &owner_cap, + external_id, + ); + + destroy_operator_cap(operator_cap); + destroy_owner_cap(owner_cap); + destroy_owner_cap(external_cap); + destroy_owner_cap(removed_cap); + destroy_operators(operators); } diff --git a/move/squid/sources/squid/coin_bag.move b/move/squid/sources/squid/coin_bag.move index 014ba484..adf339b7 100644 --- a/move/squid/sources/squid/coin_bag.move +++ b/move/squid/sources/squid/coin_bag.move @@ -1,107 +1,110 @@ -module squid::coin_bag { - use std::type_name::{Self}; +module squid::coin_bag; - use sui::bag::{Self, Bag}; - use sui::balance::{Balance}; - use sui::hash::keccak256; - use sui::address; +use std::type_name; +use sui::address; +use sui::bag::{Self, Bag}; +use sui::balance::Balance; +use sui::hash::keccak256; - public struct CoinBag has store { - bag: Bag, - } +public struct CoinBag has store { + bag: Bag, +} - public(package) fun new(ctx: &mut TxContext): CoinBag { - CoinBag{ - bag: bag::new(ctx), - } +public(package) fun new(ctx: &mut TxContext): CoinBag { + CoinBag { + bag: bag::new(ctx), } +} + +public(package) fun store_balance(self: &mut CoinBag, balance: Balance) { + let key = get_balance_key(); - public(package) fun store_balance(self: &mut CoinBag, balance: Balance) { - let key = get_balance_key(); - - if(self.bag.contains(key)) { - self.bag.borrow_mut>(key).join( + if (self.bag.contains(key)) { + self + .bag + .borrow_mut>(key) + .join( balance, ); - } else { - self.bag.add( + } else { + self + .bag + .add( key, balance, ) - } } +} - public(package) fun get_balance(self: &mut CoinBag): Option> { - let key = get_balance_key(); - - if(self.bag.contains(key)) { - option::some(self.bag.remove>(key)) - } else { - option::none>() - } +public(package) fun get_balance(self: &mut CoinBag): Option> { + let key = get_balance_key(); + + if (self.bag.contains(key)) { + option::some(self.bag.remove>(key)) + } else { + option::none>() } +} +public(package) fun get_balance_amount(self: &CoinBag): u64 { + let key = get_balance_key(); - public(package) fun get_balance_amount(self: &CoinBag): u64 { - let key = get_balance_key(); - - if(self.bag.contains(key)) { - self.bag.borrow>(key).value() - } else { - 0 - } + if (self.bag.contains(key)) { + self.bag.borrow>(key).value() + } else { + 0 } +} +public(package) fun store_estimate(self: &mut CoinBag, estimate: u64) { + let key = get_estimate_key(); - public(package) fun store_estimate(self: &mut CoinBag, estimate: u64) { - let key = get_estimate_key(); - - if(self.bag.contains(key)) { - let previous = self.bag.borrow_mut(key); - *previous = *previous + estimate; - } else { - self.bag.add( + if (self.bag.contains(key)) { + let previous = self.bag.borrow_mut(key); + *previous = *previous + estimate; + } else { + self + .bag + .add( key, estimate, ) - } } +} - public(package) fun get_estimate(self: &mut CoinBag): u64 { - let key = get_estimate_key(); - - if(self.bag.contains(key)) { - self.bag.remove(key) - } else { - 0 - } - } +public(package) fun get_estimate(self: &mut CoinBag): u64 { + let key = get_estimate_key(); - public(package) fun get_estimate_amount(self: &CoinBag): u64 { - let key = get_estimate_key(); - - if(self.bag.contains(key)) { - *self.bag.borrow(key) - } else { - 0 - } + if (self.bag.contains(key)) { + self.bag.remove(key) + } else { + 0 } +} - public(package) fun destroy(self: CoinBag) { - let CoinBag { bag } = self; - bag.destroy_empty(); +public(package) fun get_estimate_amount(self: &CoinBag): u64 { + let key = get_estimate_key(); + + if (self.bag.contains(key)) { + *self.bag.borrow(key) + } else { + 0 } +} +public(package) fun destroy(self: CoinBag) { + let CoinBag { bag } = self; + bag.destroy_empty(); +} - fun get_balance_key(): address { - let mut data = vector[0]; - data.append(type_name::get().into_string().into_bytes()); - address::from_bytes(keccak256(&data)) - } +fun get_balance_key(): address { + let mut data = vector[0]; + data.append(type_name::get().into_string().into_bytes()); + address::from_bytes(keccak256(&data)) +} - fun get_estimate_key(): address { - let mut data = vector[1]; - data.append(type_name::get().into_string().into_bytes()); - address::from_bytes(keccak256(&data)) - } -} \ No newline at end of file +fun get_estimate_key(): address { + let mut data = vector[1]; + data.append(type_name::get().into_string().into_bytes()); + address::from_bytes(keccak256(&data)) +} diff --git a/move/squid/sources/squid/deepbook_v2.move b/move/squid/sources/squid/deepbook_v2.move index 71488b1e..fd0771ad 100644 --- a/move/squid/sources/squid/deepbook_v2.move +++ b/move/squid/sources/squid/deepbook_v2.move @@ -1,392 +1,457 @@ -module squid::deepbook_v2 { - use std::type_name; - use std::ascii::{Self, String}; - - use sui::coin::{Self, Coin}; - use sui::clock::Clock; - use sui::bcs::{Self, BCS}; - - use deepbook::clob_v2::{Self as clob, Pool}; - use deepbook::custodian_v2::{Self as custodian}; - use deepbook::math as clob_math; - - use axelar_gateway::discovery::{Self, MoveCall}; - - use squid::swap_info::{SwapInfo}; - use squid::squid::Squid; - - const FLOAT_SCALING_U128: u128 = 1_000_000_000; - const FLOAT_SCALING: u64 = 1_000_000_000; - - const SWAP_TYPE: u8 = 1; - - const EWrongSwapType: u64 = 0; - const EWrongPool: u64 = 1; - const EWrongCoinType: u64 = 2; - const ENotEnoughOutput: u64 = 3; - - public struct DeepbookV2SwapData has drop{ - swap_type: u8, - pool_id: address, - has_base: bool, - min_output: u64, - base_type: String, - quote_type: String, - lot_size: u64, - should_sweep: bool, - } - - fun peel_swap_data(data: vector): DeepbookV2SwapData { - let mut bcs = bcs::new(data); - DeepbookV2SwapData { - swap_type: bcs.peel_u8(), - pool_id: bcs.peel_address(), - has_base: bcs.peel_bool(), - min_output: bcs.peel_u64(), - base_type: ascii::string(bcs.peel_vec_u8()), - quote_type: ascii::string(bcs.peel_vec_u8()), - lot_size: bcs.peel_u64(), - should_sweep: bcs.peel_bool(), - } - } - - fun swap_base(pool: &mut Pool, coin: Coin, clock: &Clock, ctx: &mut TxContext): (Coin, Coin) { - let account = clob::create_account(ctx); - let (base_coin, quote_coin, _) = pool.swap_exact_base_for_quote( - 0, - &account, - coin::value(&coin), - coin, - coin::zero(ctx), - clock, - ctx, - ); - custodian::delete_account_cap(account); - (base_coin, quote_coin) - } +module squid::deepbook_v2; + +use axelar_gateway::discovery::{Self, MoveCall}; +use deepbook::clob_v2::{Self as clob, Pool}; +use deepbook::custodian_v2 as custodian; +use deepbook::math as clob_math; +use squid::squid::Squid; +use squid::swap_info::SwapInfo; +use std::ascii::{Self, String}; +use std::type_name; +use sui::bcs::{Self, BCS}; +use sui::clock::Clock; +use sui::coin::{Self, Coin}; + +const FLOAT_SCALING_U128: u128 = 1_000_000_000; +const FLOAT_SCALING: u64 = 1_000_000_000; + +const SWAP_TYPE: u8 = 1; + +const EWrongSwapType: u64 = 0; +const EWrongPool: u64 = 1; +const EWrongCoinType: u64 = 2; +const ENotEnoughOutput: u64 = 3; + +public struct DeepbookV2SwapData has drop { + swap_type: u8, + pool_id: address, + has_base: bool, + min_output: u64, + base_type: String, + quote_type: String, + lot_size: u64, + should_sweep: bool, +} - fun swap_quote(pool: &mut Pool, coin: Coin, clock: &Clock, ctx: &mut TxContext): (Coin, Coin) { - let account = clob::create_account(ctx); - let (base_coin, quote_coin, _) = pool.swap_exact_quote_for_base( - 0, - &account, - coin::value(&coin), - clock, - coin, - ctx, - ); - custodian::delete_account_cap(account); - (base_coin, quote_coin) +fun peel_swap_data(data: vector): DeepbookV2SwapData { + let mut bcs = bcs::new(data); + DeepbookV2SwapData { + swap_type: bcs.peel_u8(), + pool_id: bcs.peel_address(), + has_base: bcs.peel_bool(), + min_output: bcs.peel_u64(), + base_type: ascii::string(bcs.peel_vec_u8()), + quote_type: ascii::string(bcs.peel_vec_u8()), + lot_size: bcs.peel_u64(), + should_sweep: bcs.peel_bool(), } +} - // multiply two floating numbers - // also returns whether the result is rounded down - fun unsafe_mul_round(x: u64, y: u64): (bool, u64) { - let x = x as u128; - let y = y as u128; - let mut is_round_down = true; - if ((x * y) % FLOAT_SCALING_U128 == 0) is_round_down = false; - (is_round_down, (x * y / FLOAT_SCALING_U128) as u64) - } +fun swap_base( + pool: &mut Pool, + coin: Coin, + clock: &Clock, + ctx: &mut TxContext, +): (Coin, Coin) { + let account = clob::create_account(ctx); + let (base_coin, quote_coin, _) = pool.swap_exact_base_for_quote( + 0, + &account, + coin::value(&coin), + coin, + coin::zero(ctx), + clock, + ctx, + ); + custodian::delete_account_cap(account); + (base_coin, quote_coin) +} - // divide two floating numbers - // also returns whether the result is rounded down - fun unsafe_div_round(x: u64, y: u64): (bool, u64) { - let x = x as u128; - let y = y as u128; - let mut is_round_down = true; - if ((x * (FLOAT_SCALING as u128) % y) == 0) is_round_down = false; - (is_round_down, (x * (FLOAT_SCALING as u128) / y) as u64) - } +fun swap_quote( + pool: &mut Pool, + coin: Coin, + clock: &Clock, + ctx: &mut TxContext, +): (Coin, Coin) { + let account = clob::create_account(ctx); + let (base_coin, quote_coin, _) = pool.swap_exact_quote_for_base( + 0, + &account, + coin::value(&coin), + clock, + coin, + ctx, + ); + custodian::delete_account_cap(account); + (base_coin, quote_coin) +} - fun get_max_quote_from_base(pool: &Pool, price: u64, depth: u64, max_base: u64) : (u64, u64) { - let filled_base_quantity = - if (max_base >= depth) { depth } - else { max_base }; - // If a bit is rounded down, the pool will take this as a fee. - let (_, mut filled_quote_quantity) = unsafe_mul_round(filled_base_quantity, price); +// multiply two floating numbers +// also returns whether the result is rounded down +fun unsafe_mul_round(x: u64, y: u64): (bool, u64) { + let x = x as u128; + let y = y as u128; + let mut is_round_down = true; + if ((x * y) % FLOAT_SCALING_U128 == 0) is_round_down = false; + (is_round_down, (x * y / FLOAT_SCALING_U128) as u64) +} - // if taker_commission = 0 due to underflow, round it up to 1 - let (is_round_down, mut taker_commission) = unsafe_mul_round( - filled_quote_quantity, - pool.taker_fee_rate(), - ); - if (is_round_down) taker_commission = taker_commission + 1; +// divide two floating numbers +// also returns whether the result is rounded down +fun unsafe_div_round(x: u64, y: u64): (bool, u64) { + let x = x as u128; + let y = y as u128; + let mut is_round_down = true; + if ((x * (FLOAT_SCALING as u128) % y) == 0) is_round_down = false; + (is_round_down, (x * (FLOAT_SCALING as u128) / y) as u64) +} - // maker in bid side, decrease maker's locked quote asset, increase maker's available base asset - filled_quote_quantity = filled_quote_quantity - taker_commission; - (filled_base_quantity, filled_quote_quantity) - } +fun get_max_quote_from_base( + pool: &Pool, + price: u64, + depth: u64, + max_base: u64, +): (u64, u64) { + let filled_base_quantity = if (max_base >= depth) { depth } else { + max_base + }; + // If a bit is rounded down, the pool will take this as a fee. + let (_, mut filled_quote_quantity) = unsafe_mul_round( + filled_base_quantity, + price, + ); + + // if taker_commission = 0 due to underflow, round it up to 1 + let (is_round_down, mut taker_commission) = unsafe_mul_round( + filled_quote_quantity, + pool.taker_fee_rate(), + ); + if (is_round_down) taker_commission = taker_commission + 1; + + // maker in bid side, decrease maker's locked quote asset, increase maker's available base asset + filled_quote_quantity = filled_quote_quantity - taker_commission; + (filled_base_quantity, filled_quote_quantity) +} - fun get_max_base_from_quote(pool: &Pool, price: u64, depth: u64, max_quote: u64, lot_size: u64) : (u64, u64) { - // Calculate how much quote asset (maker_quote_quantity) is required, including the commission, to fill the maker order. - let maker_quote_quantity_without_commission = clob_math::mul( - depth, - price, - ); - let (is_round_down, mut taker_commission) = unsafe_mul_round( - maker_quote_quantity_without_commission, - pool.taker_fee_rate(), - ); - if (is_round_down) taker_commission = taker_commission + 1; - - let maker_quote_quantity = maker_quote_quantity_without_commission + taker_commission; - - // Total base quantity filled. - let mut filled_base_quantity: u64; - // Total quote quantity filled, excluding commission and rebate. - let filled_quote_quantity: u64; - // Total quote quantity paid by taker. - // filled_quote_quantity_without_commission * (FLOAT_SCALING + taker_fee_rate) = filled_quote_quantity - let mut filled_quote_quantity_without_commission: u64; - if (max_quote > maker_quote_quantity) { - filled_quote_quantity = maker_quote_quantity; - filled_base_quantity = depth; - } else { - // if not enough quote quantity to pay for taker commission, then no quantity will be filled - (_, filled_quote_quantity_without_commission) = unsafe_div_round( +fun get_max_base_from_quote( + pool: &Pool, + price: u64, + depth: u64, + max_quote: u64, + lot_size: u64, +): (u64, u64) { + // Calculate how much quote asset (maker_quote_quantity) is required, including the commission, to fill the maker order. + let maker_quote_quantity_without_commission = clob_math::mul( + depth, + price, + ); + let (is_round_down, mut taker_commission) = unsafe_mul_round( + maker_quote_quantity_without_commission, + pool.taker_fee_rate(), + ); + if (is_round_down) taker_commission = taker_commission + 1; + + let maker_quote_quantity = maker_quote_quantity_without_commission + + taker_commission; + + // Total base quantity filled. + let mut filled_base_quantity = u64; + // Total quote quantity filled, excluding commission and rebate. + let filled_quote_quantity = u64; + // Total quote quantity paid by taker. + // filled_quote_quantity_without_commission * (FLOAT_SCALING + taker_fee_rate) = filled_quote_quantity + let mut filled_quote_quantity_without_commission = u64; + if (max_quote > maker_quote_quantity) { + filled_quote_quantity = maker_quote_quantity; + filled_base_quantity = depth; + } else { + // if not enough quote quantity to pay for taker commission, then no quantity will be filled + (_, filled_quote_quantity_without_commission) = + unsafe_div_round( max_quote, - FLOAT_SCALING + pool.taker_fee_rate() + FLOAT_SCALING + pool.taker_fee_rate(), ); - // filled_base_quantity = 0 is permitted since filled_quote_quantity_without_commission can be 0 - (_, filled_base_quantity) = unsafe_div_round( + // filled_base_quantity = 0 is permitted since filled_quote_quantity_without_commission can be 0 + (_, filled_base_quantity) = + unsafe_div_round( filled_quote_quantity_without_commission, price, ); - let filled_base_lot = filled_base_quantity / lot_size; - filled_base_quantity = filled_base_lot * lot_size; - //filled_quote_quantity_without_commission = 0 is permitted here since filled_base_quantity could be 0 - (_, filled_quote_quantity_without_commission) = unsafe_mul_round( + let filled_base_lot = filled_base_quantity / lot_size; + filled_base_quantity = filled_base_lot * lot_size; + //filled_quote_quantity_without_commission = 0 is permitted here since filled_base_quantity could be 0 + (_, filled_quote_quantity_without_commission) = + unsafe_mul_round( filled_base_quantity, price, ); - // if taker_commission = 0 due to underflow, round it up to 1 - let (round_down, mut taker_commission) = unsafe_mul_round( - filled_quote_quantity_without_commission, - pool.taker_fee_rate(), - ); - if (round_down) { - taker_commission = taker_commission + 1; - }; - filled_quote_quantity = filled_quote_quantity_without_commission + taker_commission; - }; - - (filled_quote_quantity, filled_base_quantity) - } - - fun predict_base_for_quote(pool: &Pool, amount: u64, lot_size: u64, clock: &Clock): (u64, u64) { - let max_price = (1u128 << 64 - 1 as u64); - let (prices, depths) = clob::get_level2_book_status_bid_side(pool, 0, max_price, clock); - let mut amount_left = amount; - let mut output = 0; - let mut i = prices.length(); - while(i > 0) { - i = i - 1; - let (used, max_out) = get_max_quote_from_base( - pool, - prices[i], - depths[i], - amount_left - ); - amount_left = amount_left - used; - output = output + max_out; - if(amount_left < lot_size) break; - }; - (amount_left, output) - - } - - fun predict_quote_for_base(pool: &Pool, amount: u64, lot_size: u64, clock: &Clock): (u64, u64) { - let max_price = (1u128 << 64 - 1 as u64); - let (prices, depths) = clob::get_level2_book_status_ask_side(pool, 0, max_price, clock); - - let mut amount_left = amount; - let mut output = 0; - let mut i = 0; - let length = prices.length(); - while(i < length) { - let price = prices[i]; - let (used, max_out) = get_max_base_from_quote( - pool, - price, - depths[i], - amount_left, - lot_size - ); - - amount_left = amount_left - used; - output = output + max_out; - let (_, left_base) = unsafe_div_round(amount_left, price); - if (left_base < lot_size) break; - i = i + 1; + // if taker_commission = 0 due to underflow, round it up to 1 + let (round_down, mut taker_commission) = unsafe_mul_round( + filled_quote_quantity_without_commission, + pool.taker_fee_rate(), + ); + if (round_down) { + taker_commission = taker_commission + 1; }; + filled_quote_quantity = + filled_quote_quantity_without_commission + taker_commission; + }; - (amount_left, output) - } - - public fun estimate(self: &mut SwapInfo, pool: &Pool, clock: &Clock) { - let data = self.get_data_estimating(); - if(data.length() == 0) return; - - let mut bcs = bcs::new(data); - - assert!(bcs.peel_u8() == SWAP_TYPE, EWrongSwapType); - - assert!(bcs.peel_address() == object::id_address(pool), EWrongPool); - - let has_base = bcs.peel_bool(); - let min_output = bcs.peel_u64(); + (filled_quote_quantity, filled_base_quantity) +} - assert!( - &bcs.peel_vec_u8() == &type_name::get().into_string().into_bytes(), - EWrongCoinType, +fun predict_base_for_quote( + pool: &Pool, + amount: u64, + lot_size: u64, + clock: &Clock, +): (u64, u64) { + let max_price = (1u128 << 64 - 1 as u64); + let (prices, depths) = clob::get_level2_book_status_bid_side( + pool, + 0, + max_price, + clock, + ); + let mut amount_left = amount; + let mut output = 0; + let mut i = prices.length(); + while (i > 0) { + i = i - 1; + let (used, max_out) = get_max_quote_from_base( + pool, + prices[i], + depths[i], + amount_left, ); + amount_left = amount_left - used; + output = output + max_out; + if (amount_left < lot_size) break; + }; + (amount_left, output) +} - assert!( - &bcs.peel_vec_u8() == &type_name::get().into_string().into_bytes(), - EWrongCoinType, +fun predict_quote_for_base( + pool: &Pool, + amount: u64, + lot_size: u64, + clock: &Clock, +): (u64, u64) { + let max_price = (1u128 << 64 - 1 as u64); + let (prices, depths) = clob::get_level2_book_status_ask_side( + pool, + 0, + max_price, + clock, + ); + + let mut amount_left = amount; + let mut output = 0; + let mut i = 0; + let length = prices.length(); + while (i < length) { + let price = prices[i]; + let (used, max_out) = get_max_base_from_quote( + pool, + price, + depths[i], + amount_left, + lot_size, ); - let lot_size = bcs.peel_u64(); - let should_sweep = bcs.peel_bool(); - if(has_base) { - let (amount_left, output) = predict_base_for_quote( - pool, - // these are run in sequence before anything is done with balances, so `get_estimate` is the correct function to use. - self.coin_bag().get_estimate(), - lot_size, - clock, - ); - if(min_output > output) { - self.skip_swap(); - return - }; - if(!should_sweep) self.coin_bag().store_estimate(amount_left); - self.coin_bag().store_estimate(output); - } else { - let (amount_left, output) = predict_quote_for_base( - pool, - self.coin_bag().get_estimate(), - lot_size, - clock, - ); - if(min_output > output) { - self.skip_swap(); - return - }; - if(!should_sweep) self.coin_bag().store_estimate(amount_left); - self.coin_bag().store_estimate(output); - } - } - - public fun swap(self: &mut SwapInfo, pool: &mut Pool, squid: &mut Squid, clock: &Clock, ctx: &mut TxContext) { - let data = self.get_data_swapping(); - if(data.length() == 0) return; - let swap_data = peel_swap_data(data); - - assert!(swap_data.swap_type == SWAP_TYPE, EWrongSwapType); + amount_left = amount_left - used; + output = output + max_out; + let (_, left_base) = unsafe_div_round(amount_left, price); + if (left_base < lot_size) break; + i = i + 1; + }; - assert!(swap_data.pool_id == object::id_address(pool), EWrongPool); + (amount_left, output) +} - assert!( - &swap_data.base_type == &type_name::get().into_string(), - EWrongCoinType, +public fun estimate( + self: &mut SwapInfo, + pool: &Pool, + clock: &Clock, +) { + let data = self.get_data_estimating(); + if (data.length() == 0) return; + + let mut bcs = bcs::new(data); + + assert!(bcs.peel_u8() == SWAP_TYPE, EWrongSwapType); + + assert!(bcs.peel_address() == object::id_address(pool), EWrongPool); + + let has_base = bcs.peel_bool(); + let min_output = bcs.peel_u64(); + + assert!( + &bcs.peel_vec_u8() == &type_name::get < T1 > () + .into_string() + .into_bytes(), + EWrongCoinType, + ); + + assert!( + &bcs.peel_vec_u8() == &type_name::get < T2 > () + .into_string() + .into_bytes(), + EWrongCoinType, + ); + + let lot_size = bcs.peel_u64(); + let should_sweep = bcs.peel_bool(); + if (has_base) { + let (amount_left, output) = predict_base_for_quote( + pool, + // these are run in sequence before anything is done with balances, so `get_estimate` is the correct function to use. + self.coin_bag().get_estimate(), + lot_size, + clock, ); - - assert!( - &swap_data.quote_type == &type_name::get().into_string(), - EWrongCoinType, + if (min_output > output) { + self.skip_swap(); + return + }; + if (!should_sweep) self.coin_bag().store_estimate(amount_left); + self.coin_bag().store_estimate(output); + } else { + let (amount_left, output) = predict_quote_for_base( + pool, + self.coin_bag().get_estimate(), + lot_size, + clock, ); + if (min_output > output) { + self.skip_swap(); + return + }; + if (!should_sweep) self.coin_bag().store_estimate(amount_left); + self.coin_bag().store_estimate(output); + } +} - if(swap_data.has_base) { - let mut base_balance = self.coin_bag().get_balance().destroy_some(); - let leftover = base_balance.value() % swap_data.lot_size; - if(leftover > 0) { - if(swap_data.should_sweep) { - squid.coin_bag().store_balance( - base_balance.split(leftover) - ); - } else { - self.coin_bag().store_balance( - base_balance.split(leftover), - ); - }; - }; - let (base_coin, quote_coin) = swap_base( - pool, - coin::from_balance(base_balance, ctx), - clock, - ctx, - ); - assert!(swap_data.min_output <= quote_coin.value(), ENotEnoughOutput); - base_coin.destroy_zero(); - self.coin_bag().store_balance(quote_coin.into_balance()); - } else { - let quote_balance = self.coin_bag().get_balance().destroy_some(); - let (base_coin, quote_coin) = swap_quote( - pool, - coin::from_balance(quote_balance, ctx), - clock, - ctx, - ); - assert!(swap_data.min_output <= base_coin.value(), ENotEnoughOutput); - self.coin_bag().store_balance(base_coin.into_balance()); - if(swap_data.should_sweep) { - squid.coin_bag().store_balance(quote_coin.into_balance()); +public fun swap( + self: &mut SwapInfo, + pool: &mut Pool, + squid: &mut Squid, + clock: &Clock, + ctx: &mut TxContext, +) { + let data = self.get_data_swapping(); + if (data.length() == 0) return; + let swap_data = peel_swap_data(data); + + assert!(swap_data.swap_type == SWAP_TYPE, EWrongSwapType); + + assert!(swap_data.pool_id == object::id_address(pool), EWrongPool); + + assert!( + &swap_data.base_type == &type_name::get < T1 > ().into_string(), + EWrongCoinType, + ); + + assert!( + &swap_data.quote_type == &type_name::get < T2 > ().into_string(), + EWrongCoinType, + ); + + if (swap_data.has_base) { + let mut base_balance = self.coin_bag().get_balance().destroy_some(); + let leftover = base_balance.value() % swap_data.lot_size; + if (leftover > 0) { + if (swap_data.should_sweep) { + squid + .coin_bag() + .store_balance(base_balance.split(leftover)); } else { - self.coin_bag().store_balance(quote_coin.into_balance()); + self.coin_bag().store_balance(base_balance.split(leftover)); }; - } + }; + let (base_coin, quote_coin) = swap_base( + pool, + coin::from_balance(base_balance, ctx), + clock, + ctx, + ); + assert!(swap_data.min_output <= quote_coin.value(), ENotEnoughOutput); + base_coin.destroy_zero(); + self.coin_bag().store_balance(quote_coin.into_balance()); + } else { + let quote_balance = self.coin_bag().get_balance().destroy_some(); + let (base_coin, quote_coin) = swap_quote( + pool, + coin::from_balance(quote_balance, ctx), + clock, + ctx, + ); + assert!(swap_data.min_output <= base_coin.value(), ENotEnoughOutput); + self.coin_bag().store_balance(base_coin.into_balance()); + if (swap_data.should_sweep) { + squid.coin_bag().store_balance(quote_coin.into_balance()); + } else { + self.coin_bag().store_balance(quote_coin.into_balance()); + }; } +} - public(package) fun get_estimate_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector): MoveCall { - let mut pool_arg = vector[0]; - pool_arg.append(bcs.peel_address().to_bytes()); - - let _has_base = bcs.peel_bool(); - let _min_output = bcs.peel_u64(); - - let type_base = ascii::string(bcs.peel_vec_u8()); - let type_quote = ascii::string(bcs.peel_vec_u8()); - - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"deepbook_v2"), - ascii::string(b"estimate"), - ), - vector[ - swap_info_arg, - pool_arg, - vector[0, 6], - ], - vector[type_base, type_quote], - ) - } +public(package) fun get_estimate_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, +): MoveCall { + let mut pool_arg = vector[0]; + pool_arg.append(bcs.peel_address().to_bytes()); + + let _has_base = bcs.peel_bool(); + let _min_output = bcs.peel_u64(); + + let type_base = ascii::string(bcs.peel_vec_u8()); + let type_quote = ascii::string(bcs.peel_vec_u8()); + + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"deepbook_v2"), + ascii::string(b"estimate"), + ), + vector[ + swap_info_arg, + pool_arg, + vector[0, 6], + ], + vector[type_base, type_quote], + ) +} - public(package) fun get_swap_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector, squid_arg: vector): MoveCall { - let mut pool_arg = vector[0]; - pool_arg.append(bcs.peel_address().to_bytes()); - - let _has_base = bcs.peel_bool(); - let _min_output = bcs.peel_u64(); - - let type_base = ascii::string(bcs.peel_vec_u8()); - let type_quote = ascii::string(bcs.peel_vec_u8()); - - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"deepbook_v2"), - ascii::string(b"swap"), - ), - vector[ - swap_info_arg, - pool_arg, - squid_arg, - vector[0, 6], - ], - vector[type_base, type_quote] , - ) - } +public(package) fun get_swap_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, + squid_arg: vector, +): MoveCall { + let mut pool_arg = vector[0]; + pool_arg.append(bcs.peel_address().to_bytes()); + + let _has_base = bcs.peel_bool(); + let _min_output = bcs.peel_u64(); + + let type_base = ascii::string(bcs.peel_vec_u8()); + let type_quote = ascii::string(bcs.peel_vec_u8()); + + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"deepbook_v2"), + ascii::string(b"swap"), + ), + vector[ + swap_info_arg, + pool_arg, + squid_arg, + vector[0, 6], + ], + vector[type_base, type_quote], + ) } diff --git a/move/squid/sources/squid/discovery.move b/move/squid/sources/squid/discovery.move index cb1b8f89..a391e419 100644 --- a/move/squid/sources/squid/discovery.move +++ b/move/squid/sources/squid/discovery.move @@ -1,33 +1,34 @@ -module squid::discovery { - use std::ascii::{Self, String}; - - use sui::bcs; - - use axelar_gateway::discovery::{Self, RelayerDiscovery, MoveCall, Transaction}; - - use its::its::ITS; - - use squid::squid::Squid; - use squid::transfers; - use squid::deepbook_v2; - - const EInvalidSwapType: u64 = 0; - - const SWAP_TYPE_DEEPBOOK_V2: u8 = 1; - const SWAP_TYPE_SUI_TRANSFER: u8 = 2; - const SWAP_TYPE_ITS_TRANSFER: u8 = 3; - - - public fun register_transaction(squid: &Squid, its: &ITS, relayer_discovery: &mut RelayerDiscovery) { - let mut squid_arg = vector[0]; - squid_arg.append(object::id(squid).id_to_bytes()); - - let mut its_arg = vector[0]; - its_arg.append(object::id(its).id_to_bytes()); - - let transaction = discovery::new_transaction( - false, - vector[discovery::new_move_call( +module squid::discovery; + +use axelar_gateway::discovery::{Self, RelayerDiscovery, MoveCall, Transaction}; +use its::its::ITS; +use squid::deepbook_v2; +use squid::squid::Squid; +use squid::transfers; +use std::ascii::{Self, String}; +use sui::bcs; + +const EInvalidSwapType: u64 = 0; + +const SWAP_TYPE_DEEPBOOK_V2: u8 = 1; +const SWAP_TYPE_SUI_TRANSFER: u8 = 2; +const SWAP_TYPE_ITS_TRANSFER: u8 = 3; + +public fun register_transaction( + squid: &Squid, + its: &ITS, + relayer_discovery: &mut RelayerDiscovery, +) { + let mut squid_arg = vector[0]; + squid_arg.append(object::id(squid).id_to_bytes()); + + let mut its_arg = vector[0]; + its_arg.append(object::id(its).id_to_bytes()); + + let transaction = discovery::new_transaction( + false, + vector[ + discovery::new_move_call( discovery::new_function( discovery::package_id(), ascii::string(b"discovery"), @@ -39,103 +40,151 @@ module squid::discovery { vector[3], ], vector[], - )], - ); - - relayer_discovery.register_transaction( - squid.borrow_channel(), - transaction, - ) - } - - public fun get_transaction(squid: &Squid, its: &ITS, payload: vector): Transaction { - let (token_id, _, _, data) = its::discovery::get_interchain_transfer_info(payload); - let type_in = (*its.get_registered_coin_type(token_id)).into_string(); - let package_id = discovery::package_id(); - let swap_data = bcs::new(data).peel_vec_vec_u8(); - - - let mut squid_arg = vector[0]; - squid_arg.append(object::id(squid).id_to_bytes()); - - let mut its_arg = vector[0]; - its_arg.append(object::id(its).id_to_bytes()); - let swap_info_arg = vector[4, 0, 0]; - - let mut move_calls = vector [ - start_swap(package_id, squid_arg, its_arg, type_in), - ]; - - let mut i = 0; - while(i < swap_data.length()) { - let mut bcs = bcs::new(swap_data[i]); - let swap_type = bcs.peel_u8(); - - if (swap_type == SWAP_TYPE_DEEPBOOK_V2) { - move_calls.push_back(deepbook_v2::get_estimate_move_call(package_id, bcs, swap_info_arg)); - } else if (swap_type == SWAP_TYPE_SUI_TRANSFER) { - move_calls.push_back(transfers::get_sui_estimate_move_call(package_id, bcs, swap_info_arg)); - } else { - assert!(swap_type == SWAP_TYPE_ITS_TRANSFER, EInvalidSwapType); - move_calls.push_back(transfers::get_its_estimate_move_call(package_id, bcs, swap_info_arg)); - }; - - i = i + 1; + ), + ], + ); + + relayer_discovery.register_transaction( + squid.borrow_channel(), + transaction, + ) +} + +public fun get_transaction( + squid: &Squid, + its: &ITS, + payload: vector, +): Transaction { + let (token_id, _, _, data) = its::discovery::get_interchain_transfer_info( + payload, + ); + let type_in = (*its.get_registered_coin_type(token_id)).into_string(); + let package_id = discovery::package_id(); + let swap_data = bcs::new(data).peel_vec_vec_u8(); + + let mut squid_arg = vector[0]; + squid_arg.append(object::id(squid).id_to_bytes()); + + let mut its_arg = vector[0]; + its_arg.append(object::id(its).id_to_bytes()); + let swap_info_arg = vector[4, 0, 0]; + + let mut move_calls = vector[ + start_swap(package_id, squid_arg, its_arg, type_in), + ]; + + let mut i = 0; + while (i < swap_data.length()) { + let mut bcs = bcs::new(swap_data[i]); + let swap_type = bcs.peel_u8(); + + if (swap_type == SWAP_TYPE_DEEPBOOK_V2) { + move_calls.push_back( + deepbook_v2::get_estimate_move_call( + package_id, + bcs, + swap_info_arg, + ), + ); + } else if (swap_type == SWAP_TYPE_SUI_TRANSFER) { + move_calls.push_back( + transfers::get_sui_estimate_move_call( + package_id, + bcs, + swap_info_arg, + ), + ); + } else { + assert!(swap_type == SWAP_TYPE_ITS_TRANSFER, EInvalidSwapType); + move_calls.push_back( + transfers::get_its_estimate_move_call( + package_id, + bcs, + swap_info_arg, + ), + ); }; - i = 0; - while(i < swap_data.length()) { - let mut bcs = bcs::new(swap_data[i]); - let swap_type = bcs.peel_u8(); - - if (swap_type == SWAP_TYPE_DEEPBOOK_V2) { - move_calls.push_back(deepbook_v2::get_swap_move_call(package_id, bcs, swap_info_arg, squid_arg)); - } else if (swap_type == SWAP_TYPE_SUI_TRANSFER) { - move_calls.push_back(transfers::get_sui_transfer_move_call(package_id, bcs, swap_info_arg)); - } else { - assert!(swap_type == SWAP_TYPE_ITS_TRANSFER, EInvalidSwapType); - move_calls.push_back(transfers::get_its_transfer_move_call(package_id, bcs, swap_info_arg, its_arg)); - }; - - i = i + 1; + i = i + 1; + }; + + i = 0; + while (i < swap_data.length()) { + let mut bcs = bcs::new(swap_data[i]); + let swap_type = bcs.peel_u8(); + + if (swap_type == SWAP_TYPE_DEEPBOOK_V2) { + move_calls.push_back( + deepbook_v2::get_swap_move_call( + package_id, + bcs, + swap_info_arg, + squid_arg, + ), + ); + } else if (swap_type == SWAP_TYPE_SUI_TRANSFER) { + move_calls.push_back( + transfers::get_sui_transfer_move_call( + package_id, + bcs, + swap_info_arg, + ), + ); + } else { + assert!(swap_type == SWAP_TYPE_ITS_TRANSFER, EInvalidSwapType); + move_calls.push_back( + transfers::get_its_transfer_move_call( + package_id, + bcs, + swap_info_arg, + its_arg, + ), + ); }; - move_calls.push_back(finalize(package_id, swap_info_arg)); + i = i + 1; + }; - discovery::new_transaction( - true, - move_calls, - ) - } + move_calls.push_back(finalize(package_id, swap_info_arg)); - fun start_swap(package_id: address, squid_arg: vector, its_arg: vector, type_in: String): MoveCall { - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"squid"), - ascii::string(b"start_swap"), - ), - vector[ - squid_arg, - its_arg, - vector[2], - vector[0, 6], - ], - vector[type_in], - ) - } - - fun finalize(package_id: address, swap_info_arg: vector): MoveCall { - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"swap_info"), - ascii::string(b"finalize"), - ), - vector[ - swap_info_arg, - ], - vector[], - ) - } + discovery::new_transaction( + true, + move_calls, + ) +} + +fun start_swap( + package_id: address, + squid_arg: vector, + its_arg: vector, + type_in: String, +): MoveCall { + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"squid"), + ascii::string(b"start_swap"), + ), + vector[ + squid_arg, + its_arg, + vector[2], + vector[0, 6], + ], + vector[type_in], + ) +} + +fun finalize(package_id: address, swap_info_arg: vector): MoveCall { + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"swap_info"), + ascii::string(b"finalize"), + ), + vector[ + swap_info_arg, + ], + vector[], + ) } diff --git a/move/squid/sources/squid/squid.move b/move/squid/sources/squid/squid.move index bceb606d..cb7fd009 100644 --- a/move/squid/sources/squid/squid.move +++ b/move/squid/sources/squid/squid.move @@ -1,51 +1,50 @@ -module squid::squid { - use sui::clock::Clock; - - use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; +module squid::squid; - use its::service; - use its::its::ITS; +use axelar_gateway::channel::{Self, Channel, ApprovedMessage}; +use its::its::ITS; +use its::service; +use squid::coin_bag::{Self, CoinBag}; +use squid::swap_info::{Self, SwapInfo}; +use sui::clock::Clock; - use squid::coin_bag::{Self, CoinBag}; - use squid::swap_info::{Self, SwapInfo}; - - public struct Squid has key, store{ - id: UID, - channel: Channel, - coin_bag: CoinBag, - } +public struct Squid has key, store { + id: UID, + channel: Channel, + coin_bag: CoinBag, +} - fun init(ctx: &mut TxContext) { - transfer::share_object(Squid { - id: object::new(ctx), - channel: channel::new(ctx), - coin_bag: coin_bag::new(ctx), - }); - } +fun init(ctx: &mut TxContext) { + transfer::share_object(Squid { + id: object::new(ctx), + channel: channel::new(ctx), + coin_bag: coin_bag::new(ctx), + }); +} - public(package) fun borrow_channel(self: &Squid): &Channel { - &self.channel - } +public(package) fun borrow_channel(self: &Squid): &Channel { + &self.channel +} - public fun start_swap(self: &mut Squid, its: &mut ITS, approved_message: ApprovedMessage, clock: &Clock, ctx: &mut TxContext): SwapInfo { - let (_, _, data, coin) = service::receive_interchain_transfer_with_data( - its, - approved_message, - &self.channel, - clock, - ctx, - ); - let mut swap_info = swap_info::new(data, ctx); - swap_info.coin_bag().store_estimate( - coin.value(), - ); - swap_info.coin_bag().store_balance( - coin.into_balance(), - ); - swap_info - } +public fun start_swap( + self: &mut Squid, + its: &mut ITS, + approved_message: ApprovedMessage, + clock: &Clock, + ctx: &mut TxContext, +): SwapInfo { + let (_, _, data, coin) = service::receive_interchain_transfer_with_data( + its, + approved_message, + &self.channel, + clock, + ctx, + ); + let mut swap_info = swap_info::new(data, ctx); + swap_info.coin_bag().store_estimate(coin.value()); + swap_info.coin_bag().store_balance(coin.into_balance()); + swap_info +} - public(package) fun coin_bag(self: &mut Squid): &mut CoinBag{ - &mut self.coin_bag - } +public(package) fun coin_bag(self: &mut Squid): &mut CoinBag { + &mut self.coin_bag } diff --git a/move/squid/sources/squid/swap_info.move b/move/squid/sources/squid/swap_info.move index 274a09a5..fd889ff4 100644 --- a/move/squid/sources/squid/swap_info.move +++ b/move/squid/sources/squid/swap_info.move @@ -1,103 +1,105 @@ -module squid::swap_info { - use sui::bcs; +module squid::swap_info; - use squid::coin_bag::{Self, CoinBag}; +use squid::coin_bag::{Self, CoinBag}; +use sui::bcs; - public struct SwapInfo { - swap_index: u64, - estimate_index: u64, - status: u8, - swap_data: vector>, - coin_bag: CoinBag, - } - - const ESTIMATING: u8 = 0; - const SWAPPING: u8 = 1; - const SKIP_SWAP: u8 = 2; - - const EOutOfEstimates: u64 = 0; - const EOutOfSwaps: u64 = 1; - const ENotEstimating: u64 = 3; - const ENotSwapping: u64 = 4; - const ENotDoneEstimating: u64 = 5; - const ENotDoneSwapping: u64 = 6; - - - public(package) fun new(data: vector, ctx: &mut TxContext): SwapInfo { - let swap_data = bcs::new(data).peel_vec_vec_u8(); - SwapInfo { - swap_index: 0, - estimate_index: 0, - status: ESTIMATING, - coin_bag: coin_bag::new(ctx), +public struct SwapInfo { + swap_index: u64, + estimate_index: u64, + status: u8, + swap_data: vector>, + coin_bag: CoinBag, +} - swap_data, - } +const ESTIMATING: u8 = 0; +const SWAPPING: u8 = 1; +const SKIP_SWAP: u8 = 2; + +const EOutOfEstimates: u64 = 0; +const EOutOfSwaps: u64 = 1; +const ENotEstimating: u64 = 3; +const ENotSwapping: u64 = 4; +const ENotDoneEstimating: u64 = 5; +const ENotDoneSwapping: u64 = 6; + +public(package) fun new(data: vector, ctx: &mut TxContext): SwapInfo { + let swap_data = bcs::new(data).peel_vec_vec_u8(); + SwapInfo { + swap_index: 0, + estimate_index: 0, + status: ESTIMATING, + coin_bag: coin_bag::new(ctx), + swap_data, } +} - public(package) fun get_data_swapping(self: &mut SwapInfo): vector { - let index = self.swap_index; - if(index == 0 && self.status == ESTIMATING) { - assert!(self.estimate_index == self.swap_data.length(), ENotDoneEstimating); - self.status = SWAPPING; - }; - assert!(index < self.swap_data.length(), EOutOfSwaps); - - self.swap_index = index + 1; - if(self.status == SKIP_SWAP) { - vector[] - } else { - assert!(self.status == SWAPPING, ENotSwapping); - self.swap_data[index] - } +public(package) fun get_data_swapping(self: &mut SwapInfo): vector { + let index = self.swap_index; + if (index == 0 && self.status == ESTIMATING) { + assert!( + self.estimate_index == self.swap_data.length(), + ENotDoneEstimating, + ); + self.status = SWAPPING; + }; + assert!(index < self.swap_data.length(), EOutOfSwaps); + + self.swap_index = index + 1; + if (self.status == SKIP_SWAP) { + vector[] + } else { + assert!(self.status == SWAPPING, ENotSwapping); + self.swap_data[index] } +} - public(package) fun get_data_estimating(self: &mut SwapInfo): vector { - let index = self.estimate_index; - assert!(index < self.swap_data.length(), EOutOfEstimates); +public(package) fun get_data_estimating(self: &mut SwapInfo): vector { + let index = self.estimate_index; + assert!(index < self.swap_data.length(), EOutOfEstimates); - self.estimate_index = index + 1; + self.estimate_index = index + 1; - if (self.status == SKIP_SWAP) { - vector[] - } else { - assert!(self.status == ESTIMATING, ENotEstimating); - self.swap_data[index] - } + if (self.status == SKIP_SWAP) { + vector[] + } else { + assert!(self.status == ESTIMATING, ENotEstimating); + self.swap_data[index] } +} - public(package) fun coin_bag(self: &mut SwapInfo): &mut CoinBag { - &mut self.coin_bag - } - public(package) fun swap_data(self: &SwapInfo, i: u64): vector { - self.swap_data[i] - } +public(package) fun coin_bag(self: &mut SwapInfo): &mut CoinBag { + &mut self.coin_bag +} - public(package) fun skip_swap(self: &mut SwapInfo) { - self.status = SKIP_SWAP; - } +public(package) fun swap_data(self: &SwapInfo, i: u64): vector { + self.swap_data[i] +} - public fun finalize(self: SwapInfo) { - assert!( - self.estimate_index == self.swap_data.length() && - self.swap_index == self.swap_data.length(), - ENotDoneSwapping, - ); - assert!( - self.status == SWAPPING || - self.status == SKIP_SWAP, ENotDoneSwapping - ); - self.destroy(); - } +public(package) fun skip_swap(self: &mut SwapInfo) { + self.status = SKIP_SWAP; +} - fun destroy(self: SwapInfo) { - let SwapInfo { - swap_index: _, - estimate_index: _, - swap_data: _, - status: _, - coin_bag, - } = self; - coin_bag.destroy(); - } +public fun finalize(self: SwapInfo) { + assert!( + self.estimate_index == self.swap_data.length() && + self.swap_index == self.swap_data.length(), + ENotDoneSwapping, + ); + assert!( + self.status == SWAPPING || + self.status == SKIP_SWAP, + ENotDoneSwapping, + ); + self.destroy(); +} + +fun destroy(self: SwapInfo) { + let SwapInfo { + swap_index: _, + estimate_index: _, + swap_data: _, + status: _, + coin_bag, + } = self; + coin_bag.destroy(); } diff --git a/move/squid/sources/squid/transfers.move b/move/squid/sources/squid/transfers.move index d8afccf1..975fcef4 100644 --- a/move/squid/sources/squid/transfers.move +++ b/move/squid/sources/squid/transfers.move @@ -1,201 +1,222 @@ -module squid::transfers { - use std::type_name; - use std::ascii::{Self, String}; - - use sui::bcs::{Self, BCS}; - use sui::coin; - use sui::clock::Clock; - - use axelar_gateway::discovery::{Self, MoveCall}; - - use its::service; - use its::its::ITS; - use its::token_id::{Self, TokenId}; - - use squid::swap_info::{SwapInfo}; - - const SWAP_TYPE_SUI_TRANSFER: u8 = 2; - const SWAP_TYPE_ITS_TRANSFER: u8 = 3; +module squid::transfers; + +use axelar_gateway::discovery::{Self, MoveCall}; +use its::its::ITS; +use its::service; +use its::token_id::{Self, TokenId}; +use squid::swap_info::SwapInfo; +use std::ascii::{Self, String}; +use std::type_name; +use sui::bcs::{Self, BCS}; +use sui::clock::Clock; +use sui::coin; + +const SWAP_TYPE_SUI_TRANSFER: u8 = 2; +const SWAP_TYPE_ITS_TRANSFER: u8 = 3; + +const EWrongSwapType: u64 = 0; +const EWrongCoinType: u64 = 1; + +public struct SuiTransferSwapData has drop { + swap_type: u8, + coin_type: String, + recipient: address, +} - const EWrongSwapType: u64 = 0; - const EWrongCoinType: u64 = 1; +public struct ItsTransferSwapData has drop { + swap_type: u8, + coin_type: String, + token_id: TokenId, + destination_chain: String, + destination_address: vector, + metadata: vector, +} - public struct SuiTransferSwapData has drop { - swap_type: u8, - coin_type: String, - recipient: address, +fun new_sui_transfer_swap_data(data: vector): SuiTransferSwapData { + let mut bcs = bcs::new(data); + SuiTransferSwapData { + swap_type: bcs.peel_u8(), + coin_type: ascii::string(bcs.peel_vec_u8()), + recipient: bcs.peel_address(), } +} - public struct ItsTransferSwapData has drop { - swap_type: u8, - coin_type: String, - token_id: TokenId, - destination_chain: String, - destination_address: vector, - metadata: vector, +fun new_its_transfer_swap_data(data: vector): ItsTransferSwapData { + let mut bcs = bcs::new(data); + ItsTransferSwapData { + swap_type: bcs.peel_u8(), + coin_type: ascii::string(bcs.peel_vec_u8()), + token_id: token_id::from_address(bcs.peel_address()), + destination_chain: ascii::string(bcs.peel_vec_u8()), + destination_address: bcs.peel_vec_u8(), + metadata: bcs.peel_vec_u8(), } +} - fun new_sui_transfer_swap_data(data: vector): SuiTransferSwapData { - let mut bcs = bcs::new(data); - SuiTransferSwapData { - swap_type: bcs.peel_u8(), - coin_type: ascii::string(bcs.peel_vec_u8()), - recipient: bcs.peel_address(), - } - } +public fun sui_estimate(swap_info: &mut SwapInfo) { + let data = swap_info.get_data_estimating(); + if (data.length() == 0) return; + let swap_data = new_sui_transfer_swap_data(data); - fun new_its_transfer_swap_data(data: vector): ItsTransferSwapData { - let mut bcs = bcs::new(data); - ItsTransferSwapData { - swap_type: bcs.peel_u8(), - coin_type: ascii::string(bcs.peel_vec_u8()), - token_id: token_id::from_address(bcs.peel_address()), - destination_chain: ascii::string(bcs.peel_vec_u8()), - destination_address: bcs.peel_vec_u8(), - metadata: bcs.peel_vec_u8(), - } - } + assert!(swap_data.swap_type == SWAP_TYPE_SUI_TRANSFER, EWrongSwapType); - public fun sui_estimate(swap_info: &mut SwapInfo) { - let data = swap_info.get_data_estimating(); - if (data.length() == 0) return; - let swap_data = new_sui_transfer_swap_data(data); + assert!( + &swap_data.coin_type == &type_name::get < T > ().into_string(), + EWrongCoinType, + ); - assert!(swap_data.swap_type == SWAP_TYPE_SUI_TRANSFER, EWrongSwapType); + swap_info.coin_bag().get_estimate(); +} - assert!( - &swap_data.coin_type == &type_name::get().into_string(), - EWrongCoinType, - ); +public fun its_estimate(swap_info: &mut SwapInfo) { + let data = swap_info.get_data_estimating(); + if (data.length() == 0) return; + let swap_data = new_its_transfer_swap_data(data); - swap_info.coin_bag().get_estimate(); - } + assert!(swap_data.swap_type == SWAP_TYPE_ITS_TRANSFER, EWrongSwapType); + + assert!( + &swap_data.coin_type == &type_name::get < T > ().into_string(), + EWrongCoinType, + ); - public fun its_estimate(swap_info: &mut SwapInfo) { - let data = swap_info.get_data_estimating(); - if (data.length() == 0) return; - let swap_data = new_its_transfer_swap_data(data); + swap_info.coin_bag().get_estimate(); +} - assert!(swap_data.swap_type == SWAP_TYPE_ITS_TRANSFER, EWrongSwapType); +public fun sui_transfer(swap_info: &mut SwapInfo, ctx: &mut TxContext) { + let data = swap_info.get_data_swapping(); + if (data.length() == 0) return; + let swap_data = new_sui_transfer_swap_data(data); - assert!( - &swap_data.coin_type == &type_name::get().into_string(), - EWrongCoinType, - ); + assert!(swap_data.swap_type == SWAP_TYPE_SUI_TRANSFER, EWrongSwapType); - swap_info.coin_bag().get_estimate(); - } + assert!( + &swap_data.coin_type == &type_name::get < T > ().into_string(), + EWrongCoinType, + ); - public fun sui_transfer(swap_info: &mut SwapInfo, ctx: &mut TxContext) { - let data = swap_info.get_data_swapping(); - if (data.length() == 0) return; - let swap_data = new_sui_transfer_swap_data(data); - - assert!(swap_data.swap_type == SWAP_TYPE_SUI_TRANSFER, EWrongSwapType); - - assert!( - &swap_data.coin_type == &type_name::get().into_string(), - EWrongCoinType, - ); - - let option = swap_info.coin_bag().get_balance(); - if(option.is_none()) { - option.destroy_none(); - return - }; - - transfer::public_transfer(coin::from_balance(option.destroy_some(), ctx), swap_data.recipient); - } + let option = swap_info.coin_bag().get_balance(); + if (option.is_none()) { + option.destroy_none(); + return + }; - public fun its_transfer(swap_info: &mut SwapInfo, its: &mut ITS, clock: &Clock, ctx: &mut TxContext) { - let data = swap_info.get_data_swapping(); - if (data.length() == 0) return; - let swap_data = new_its_transfer_swap_data(data); - - assert!(swap_data.swap_type == SWAP_TYPE_ITS_TRANSFER, EWrongSwapType); - - assert!( - &swap_data.coin_type == &type_name::get().into_string(), - EWrongCoinType, - ); - - let option = swap_info.coin_bag().get_balance(); - if(option.is_none()) { - option.destroy_none(); - return - }; - - service::interchain_transfer( - its, - swap_data.token_id, - coin::from_balance(option.destroy_some(), ctx), - swap_data.destination_chain, - swap_data.destination_address, - swap_data.metadata, - clock, - ctx, - ); - } + transfer::public_transfer( + coin::from_balance(option.destroy_some(), ctx), + swap_data.recipient, + ); +} - public(package) fun get_sui_estimate_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector): MoveCall { - let type_arg = ascii::string(bcs.peel_vec_u8()); - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"transfers"), - ascii::string(b"sui_estimate"), - ), - vector[ - swap_info_arg, - ], - vector[type_arg], - ) - } +public fun its_transfer( + swap_info: &mut SwapInfo, + its: &mut ITS, + clock: &Clock, + ctx: &mut TxContext, +) { + let data = swap_info.get_data_swapping(); + if (data.length() == 0) return; + let swap_data = new_its_transfer_swap_data(data); + + assert!(swap_data.swap_type == SWAP_TYPE_ITS_TRANSFER, EWrongSwapType); + + assert!( + &swap_data.coin_type == &type_name::get < T > ().into_string(), + EWrongCoinType, + ); + + let option = swap_info.coin_bag().get_balance(); + if (option.is_none()) { + option.destroy_none(); + return + }; + + service::interchain_transfer( + its, + swap_data.token_id, + coin::from_balance(option.destroy_some(), ctx), + swap_data.destination_chain, + swap_data.destination_address, + swap_data.metadata, + clock, + ctx, + ); +} - public(package) fun get_its_estimate_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector): MoveCall { - let type_arg = ascii::string(bcs.peel_vec_u8()); - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"transfers"), - ascii::string(b"its_estimate"), - ), - vector[ - swap_info_arg, - ], - vector[type_arg], - ) - } +public(package) fun get_sui_estimate_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, +): MoveCall { + let type_arg = ascii::string(bcs.peel_vec_u8()); + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"transfers"), + ascii::string(b"sui_estimate"), + ), + vector[ + swap_info_arg, + ], + vector[type_arg], + ) +} - public(package) fun get_sui_transfer_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector): MoveCall { - let type_arg = ascii::string(bcs.peel_vec_u8()); - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"transfers"), - ascii::string(b"sui_transfer"), - ), - vector[ - swap_info_arg, - ], - vector[type_arg], - ) - } +public(package) fun get_its_estimate_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, +): MoveCall { + let type_arg = ascii::string(bcs.peel_vec_u8()); + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"transfers"), + ascii::string(b"its_estimate"), + ), + vector[ + swap_info_arg, + ], + vector[type_arg], + ) +} - public(package) fun get_its_transfer_move_call(package_id: address, mut bcs: BCS, swap_info_arg: vector, its_arg: vector): MoveCall { - let type_arg = ascii::string(bcs.peel_vec_u8()); - discovery::new_move_call( - discovery::new_function( - package_id, - ascii::string(b"transfers"), - ascii::string(b"its_transfer"), - ), - vector[ - swap_info_arg, - its_arg, - vector[0, 6], - ], - vector[type_arg], - ) - } +public(package) fun get_sui_transfer_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, +): MoveCall { + let type_arg = ascii::string(bcs.peel_vec_u8()); + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"transfers"), + ascii::string(b"sui_transfer"), + ), + vector[ + swap_info_arg, + ], + vector[type_arg], + ) +} + +public(package) fun get_its_transfer_move_call( + package_id: address, + mut bcs: BCS, + swap_info_arg: vector, + its_arg: vector, +): MoveCall { + let type_arg = ascii::string(bcs.peel_vec_u8()); + discovery::new_move_call( + discovery::new_function( + package_id, + ascii::string(b"transfers"), + ascii::string(b"its_transfer"), + ), + vector[ + swap_info_arg, + its_arg, + vector[0, 6], + ], + vector[type_arg], + ) }