From 26a618fb794f1a7d8490f3595be2811108a03c1e Mon Sep 17 00:00:00 2001 From: npty <78221556+npty@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:44:26 +0700 Subject: [PATCH] chore: format by Prettier Move plugin (#109) Co-authored-by: Phuchit Sirimongkhonsathian Co-authored-by: Milap Sheth --- .changeset/honest-yaks-prove.md | 5 + .github/actions/install/action.yaml | 23 +- .github/workflows/codecov.yaml | 5 - .github/workflows/create-release-pr.yaml | 3 - .github/workflows/publish-to-npm.yaml | 5 - .github/workflows/release-snapshot.yaml | 5 - .github/workflows/test-js.yaml | 10 +- .github/workflows/test-move.yaml | 5 - move/abi/Move.lock | 2 +- move/abi/sources/abi.move | 524 ++++---- move/axelar_gateway/Move.lock | 2 +- move/axelar_gateway/sources/auth.move | 463 +++---- move/axelar_gateway/sources/channel.move | 479 +++---- move/axelar_gateway/sources/discovery.move | 541 ++++---- move/axelar_gateway/sources/gateway.move | 1155 +++++++++-------- .../axelar_gateway/sources/types/bytes32.move | 92 +- .../axelar_gateway/sources/types/message.move | 126 +- move/axelar_gateway/sources/types/proof.move | 165 +-- .../sources/types/weighted_signer.move | 140 +- .../sources/types/weighted_signers.move | 173 ++- move/example/Move.lock | 2 +- move/example/sources/gmp/gmp.move | 166 +-- move/gas_service/Move.lock | 2 +- move/gas_service/sources/gas_service.move | 693 +++++----- move/governance/Move.lock | 2 +- .../sources/governance/governance.move | 877 +++++++------ move/interchain_token/Move.lock | 2 +- .../sources/interchain_token.move | 36 +- move/its/Move.lock | 2 +- move/operators/Move.lock | 2 +- move/operators/sources/operators.move | 682 +++++----- move/squid/Move.lock | 2 +- src/tx-builder.ts | 6 +- src/utils.ts | 17 + version.json | 3 + 35 files changed, 3341 insertions(+), 3076 deletions(-) create mode 100644 .changeset/honest-yaks-prove.md create mode 100644 version.json diff --git a/.changeset/honest-yaks-prove.md b/.changeset/honest-yaks-prove.md new file mode 100644 index 00000000..0c2a308b --- /dev/null +++ b/.changeset/honest-yaks-prove.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': minor +--- + +Formatted move modules with "Prettier Move" plugin to compatible with Sui 1.31.1 diff --git a/.github/actions/install/action.yaml b/.github/actions/install/action.yaml index 89e2e9fb..e14aae08 100644 --- a/.github/actions/install/action.yaml +++ b/.github/actions/install/action.yaml @@ -1,18 +1,15 @@ name: Install Dependencies description: 'Setup Sui CLI and install dependencies' -inputs: - SUI_VERSION: - description: 'The version of Sui CLI to install' - required: true - runs: using: 'composite' steps: - - name: Debug Action Input + - name: Get Sui Version shell: bash - run: echo "SUI_VERSION=${{ inputs.SUI_VERSION }}" + run: | + SUI_VERSION=$(jq -r '.SUI_VERSION' version.json) + echo "SUI_VERSION=$SUI_VERSION" >> $GITHUB_ENV - name: Install Dependencies shell: bash @@ -25,19 +22,18 @@ runs: uses: actions/cache@v4 with: path: sui-binaries/ - key: sui-${{ inputs.SUI_VERSION }} + key: sui-${{ env.SUI_VERSION }} - name: Download and Install Sui shell: bash if: steps.cache-sui.outputs.cache-hit != 'true' run: | - curl -L -o sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz https://github.com/MystenLabs/sui/releases/download/${{ inputs.SUI_VERSION }}/sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz - tar -xvf sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz + curl -L -o sui-${{ env.SUI_VERSION }}-ubuntu-x86_64.tgz https://github.com/MystenLabs/sui/releases/download/${{ env.SUI_VERSION }}/sui-${{ env.SUI_VERSION }}-ubuntu-x86_64.tgz + tar -xvf sui-${{ env.SUI_VERSION }}-ubuntu-x86_64.tgz mkdir -p sui-binaries mv ./sui ./sui-binaries/ mv ./sui-debug ./sui-binaries/ - mv ./sui-test-validator ./sui-binaries/ - rm -rf sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz + rm -rf sui-${{ env.SUI_VERSION }}-ubuntu-x86_64.tgz - name: Save Sui binaries if: steps.cache-sui.outputs.cache-hit != 'true' @@ -45,14 +41,13 @@ runs: uses: actions/cache@v4 with: path: sui-binaries/ - key: sui-${{ inputs.SUI_VERSION }} + key: sui-${{ env.SUI_VERSION }} - name: Add Sui binaries to PATH shell: bash run: | sudo cp ./sui-binaries/sui /usr/local/bin/sui sudo cp ./sui-binaries/sui-debug /usr/local/bin/sui-debug - sudo cp ./sui-binaries/sui-test-validator /usr/local/bin/sui-test-validator - name: Setup Node uses: actions/setup-node@v4 diff --git a/.github/workflows/codecov.yaml b/.github/workflows/codecov.yaml index e0d2b8dc..995ef4cc 100644 --- a/.github/workflows/codecov.yaml +++ b/.github/workflows/codecov.yaml @@ -6,9 +6,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: coverage: runs-on: ubuntu-latest @@ -19,8 +16,6 @@ jobs: - name: Setup Sui CLI and install dependencies uses: ./.github/actions/install - with: - SUI_VERSION: ${{ env.SUI_VERSION }} - name: Coverage run: npm run coverage diff --git a/.github/workflows/create-release-pr.yaml b/.github/workflows/create-release-pr.yaml index 2917a4a1..b71dd6af 100644 --- a/.github/workflows/create-release-pr.yaml +++ b/.github/workflows/create-release-pr.yaml @@ -5,9 +5,6 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: release-pr: name: Create Release Pull Request diff --git a/.github/workflows/publish-to-npm.yaml b/.github/workflows/publish-to-npm.yaml index f0bbb248..a3ad5baf 100644 --- a/.github/workflows/publish-to-npm.yaml +++ b/.github/workflows/publish-to-npm.yaml @@ -9,9 +9,6 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: publish: name: Publish to NPM @@ -34,8 +31,6 @@ jobs: - name: Setup Sui CLI and install dependencies uses: ./.github/actions/install - with: - SUI_VERSION: ${{ env.SUI_VERSION }} - name: Create GitHub Release if: steps.check-changeset-files.outputs.has_changeset_files == 'false' diff --git a/.github/workflows/release-snapshot.yaml b/.github/workflows/release-snapshot.yaml index bd95c35f..8704c64f 100644 --- a/.github/workflows/release-snapshot.yaml +++ b/.github/workflows/release-snapshot.yaml @@ -5,9 +5,6 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: release-snapshot: name: Release Snapshot @@ -18,8 +15,6 @@ jobs: - name: Setup Sui CLI and install dependencies uses: ./.github/actions/install - with: - SUI_VERSION: ${{ env.SUI_VERSION }} - name: Build TS and SUI modules run: npm run build diff --git a/.github/workflows/test-js.yaml b/.github/workflows/test-js.yaml index e68707af..bb6d0c1c 100644 --- a/.github/workflows/test-js.yaml +++ b/.github/workflows/test-js.yaml @@ -11,9 +11,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: test-js: runs-on: ubuntu-latest @@ -23,14 +20,15 @@ jobs: - name: Setup Sui CLI and install dependencies uses: ./.github/actions/install - with: - SUI_VERSION: ${{ env.SUI_VERSION }} - name: Build TS and Move Modules run: npm run build + - name: Initialize Sui Config + run: echo -e "y\n\n1" | sui client envs + - name: Run Localnet - run: nohup sh -c "sui-test-validator" > nohup.out 2> nohup.err < /dev/null & + run: nohup sh -c "RUST_LOG="off,sui_node=info" sui start --with-faucet --force-regenesis" > nohup.out 2> nohup.err < /dev/null & - name: Sleep for 30 seconds run: sleep 30s diff --git a/.github/workflows/test-move.yaml b/.github/workflows/test-move.yaml index 63dd099e..8546a0e3 100644 --- a/.github/workflows/test-move.yaml +++ b/.github/workflows/test-move.yaml @@ -11,9 +11,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - SUI_VERSION: mainnet-v1.25.3 - jobs: test-move: runs-on: ubuntu-latest @@ -23,8 +20,6 @@ jobs: - name: Setup Sui CLI and install dependencies uses: ./.github/actions/install - with: - SUI_VERSION: ${{ env.SUI_VERSION }} - name: Build Move Modules run: npm run build-move diff --git a/move/abi/Move.lock b/move/abi/Move.lock index 497723bf..f86574e8 100644 --- a/move/abi/Move.lock +++ b/move/abi/Move.lock @@ -21,6 +21,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" 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/Move.lock b/move/axelar_gateway/Move.lock index 87e9902b..d373b51b 100644 --- a/move/axelar_gateway/Move.lock +++ b/move/axelar_gateway/Move.lock @@ -21,6 +21,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" diff --git a/move/axelar_gateway/sources/auth.move b/move/axelar_gateway/sources/auth.move index 3508b08f..5e1bdf7d 100644 --- a/move/axelar_gateway/sources/auth.move +++ b/move/axelar_gateway/sources/auth.move @@ -1,253 +1,268 @@ -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; +} - #[test_only] - public fun dummy(ctx: &mut TxContext): AxelarSigners { - AxelarSigners { - epoch: 0, - epoch_by_signers_hash: table::new(ctx), - domain_separator: bytes32::new(@0x1), - minimum_rotation_delay: 1, - last_rotation_timestamp: 0, - previous_signers_retention: 3, - } +#[test_only] +public fun dummy(ctx: &mut TxContext): AxelarSigners { + AxelarSigners { + epoch: 0, + epoch_by_signers_hash: table::new(ctx), + domain_separator: bytes32::new(@0x1), + minimum_rotation_delay: 1, + last_rotation_timestamp: 0, + previous_signers_retention: 3, } +} - #[test_only] - public fun destroy_for_testing(signers: AxelarSigners): ( - u64, - Table, - Bytes32, - u64, - u64, - u64, - ) { - let AxelarSigners { - epoch, - epoch_by_signers_hash, - domain_separator, - minimum_rotation_delay, - last_rotation_timestamp, - previous_signers_retention, - } = signers; - ( - epoch, - epoch_by_signers_hash, - domain_separator, - minimum_rotation_delay, - last_rotation_timestamp, - previous_signers_retention, - ) - } +#[test_only] +public fun destroy_for_testing( + signers: AxelarSigners, +): (u64, Table, Bytes32, u64, u64, u64) { + let AxelarSigners { + epoch, + epoch_by_signers_hash, + domain_separator, + minimum_rotation_delay, + last_rotation_timestamp, + previous_signers_retention, + } = signers; + ( + epoch, + epoch_by_signers_hash, + domain_separator, + minimum_rotation_delay, + last_rotation_timestamp, + previous_signers_retention, + ) } diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 52241ece..f85f98f8 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -3,273 +3,274 @@ /// 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_only] - public fun destroy_for_testing( - approved_message: ApprovedMessage - ) { - ApprovedMessage { - source_chain: _, - message_id: _, - source_address: _, - destination_id: _, - payload: _, - } = approved_message; - } +/// 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_new_and_destroy() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - 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_id() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - assert!(channel.id() == object::id(&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_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_only] +public fun destroy_for_testing(approved_message: ApprovedMessage) { + ApprovedMessage { + source_chain: _, + message_id: _, + source_address: _, + destination_id: _, + payload: _, + } = approved_message; +} - #[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 3ac76fe4..1845c05c 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -25,642 +25,695 @@ /// - 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()); - } +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, +} - /// 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); - } +/// 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, +} - // ----------- - // Entrypoints - // ----------- +// ------------ +// Capabilities +// ------------ +public struct CreatorCap has key, store { + id: UID, +} - /// 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); +// ------ +// 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, +} - let _ = self.signers.validate_proof(data_hash(COMMAND_TYPE_APPROVE_MESSAGES, message_data), proof); +/// Emitted when a new message is approved by the gateway. +public struct MessageApproved has copy, drop { + message: message::Message, +} - let mut i = 0; +/// Emitted when a message is taken to be executed by a channel. +public struct MessageExecuted has copy, drop { + message: message::Message, +} - while (i < messages.length()) { - self.approve_message(&messages[i]); +// ----- +// Setup +// ----- - i = i + 1; - }; - } +/// 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), + }; - /// 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); - } + transfer::transfer(cap, ctx.sender()); +} - // ---------------- - // 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)), - }) - } +/// 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); +} - 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, +// ----------- +// 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 command_id = message.command_id(); - self.messages[command_id].status == message.hash() - } + let mut i = 0; - 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, - ); + while (i < messages.length()) { + self.approve_message(&messages[i]); - self.messages[command_id].status == bytes32::new(@0x1) - } + i = i + 1; + }; +} - /// 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)), +/// 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, - }); - } - - #[test_only] - public fun create_for_testing( - operator: address, - domain_separator: Bytes32, - minimum_rotation_delay: u64, - previous_signers_retention: u64, - initial_signers: WeightedSigners, - clock: &Clock, - ctx: &mut TxContext - ): Gateway { - Gateway { - id: object::new(ctx), - operator, - messages: table::new(ctx), - signers: auth::setup(domain_separator, minimum_rotation_delay, previous_signers_retention, initial_signers, clock, ctx), - } - } - - #[test_only] - public fun dummy(ctx: &mut TxContext): Gateway { - Gateway { - id: object::new(ctx), - operator: @0x0, - messages: table::new(ctx), - signers: auth::dummy(ctx), - } - } - - #[test_only] - public fun destroy_for_testing(gateway: Gateway) { - let Gateway { - id, - operator: _, - messages, - signers, - } = gateway; - - id.delete(); - let (_, table, _, _, _, _) = signers.destroy_for_testing(); - table.destroy_empty(); - messages.destroy_empty(); - } + sui::event::emit(MessageApproved { + message: *message, + }); +} - #[test] - fun test_setup() { - let ctx = &mut sui::tx_context::dummy(); - let operator = @123456; - let domain_separator = bytes32::new(@789012); - let minimum_rotation_delay = 765; - let previous_signers_retention = 650; - let initial_signers = axelar_gateway::weighted_signers::dummy(); - let mut clock = sui::clock::create_for_testing(ctx); - let timestamp = 1234; - clock.increment_for_testing(timestamp); - - let creator_cap = CreatorCap { - id: object::new(ctx), - }; - - let mut scenario = sui::test_scenario::begin(@0x1); - - setup( - creator_cap, - operator, +#[test_only] +public fun create_for_testing( + operator: address, + domain_separator: Bytes32, + minimum_rotation_delay: u64, + previous_signers_retention: u64, + initial_signers: WeightedSigners, + clock: &Clock, + ctx: &mut TxContext, +): Gateway { + Gateway { + id: object::new(ctx), + operator, + messages: table::new(ctx), + signers: auth::setup( domain_separator, minimum_rotation_delay, previous_signers_retention, - bcs::to_bytes(&initial_signers), - &clock, - scenario.ctx(), - ); - - let tx_effects = scenario.next_tx(@0x1); - let shared = tx_effects.shared(); - - assert!(shared.length() == 1, 0); - - let gateway_id = shared[0]; - let gateway = scenario.take_shared_by_id(gateway_id); - - let Gateway { - id, - operator: operator_result, - messages, - signers, - } = { gateway }; - - id.delete(); - assert!(operator == operator_result, 1); - messages.destroy_empty(); - - let ( - epoch, - mut epoch_by_signers_hash, - domain_separator_result, - minimum_rotation_delay_result, - last_rotation_timestamp, - previous_signers_retention_result, - ) = signers.destroy_for_testing(); - - let signer_epoch = epoch_by_signers_hash.remove(initial_signers.hash()); - epoch_by_signers_hash.destroy_empty(); - - assert!(epoch == 1, 2); - assert!(signer_epoch == 1, 3); - assert!(domain_separator == domain_separator_result, 4); - assert!(minimum_rotation_delay == minimum_rotation_delay_result, 5); - assert!(last_rotation_timestamp == timestamp, 6); - assert!(previous_signers_retention == previous_signers_retention_result, 7); - - - clock.destroy_for_testing(); - scenario.end(); + initial_signers, + clock, + ctx, + ), } +} - #[test] - fun test_approve_message() { - let ctx = &mut sui::tx_context::dummy(); - - let message_id = std::ascii::string(b"Message Id"); - let channel = axelar_gateway::channel::new(ctx); - let source_chain = std::ascii::string(b"Source Chain"); - let source_address = std::ascii::string(b"Destination Address"); - let payload = vector[0, 1, 2, 3]; - let payload_hash = axelar_gateway::bytes32::new(address::from_bytes(hash::keccak256(&payload))); - - let message = message::new( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ); +#[test_only] +public fun dummy(ctx: &mut TxContext): Gateway { + Gateway { + id: object::new(ctx), + operator: @0x0, + messages: table::new(ctx), + signers: auth::dummy(ctx), + } +} - let mut gateway = dummy(ctx); +#[test_only] +public fun destroy_for_testing(gateway: Gateway) { + let Gateway { + id, + operator: _, + messages, + signers, + } = gateway; + + id.delete(); + let (_, table, _, _, _, _) = signers.destroy_for_testing(); + table.destroy_empty(); + messages.destroy_empty(); +} - approve_message(&mut gateway, &message); - // The second approve message should do nothing. - approve_message(&mut gateway, &message); +#[test] +fun test_setup() { + let ctx = &mut sui::tx_context::dummy(); + let operator = @123456; + let domain_separator = bytes32::new(@789012); + let minimum_rotation_delay = 765; + let previous_signers_retention = 650; + let initial_signers = axelar_gateway::weighted_signers::dummy(); + let mut clock = sui::clock::create_for_testing(ctx); + let timestamp = 1234; + clock.increment_for_testing(timestamp); + + let creator_cap = CreatorCap { + id: object::new(ctx), + }; + + let mut scenario = sui::test_scenario::begin(@0x1); + + setup( + creator_cap, + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + bcs::to_bytes(&initial_signers), + &clock, + scenario.ctx(), + ); + + let tx_effects = scenario.next_tx(@0x1); + let shared = tx_effects.shared(); + + assert!(shared.length() == 1, 0); + + let gateway_id = shared[0]; + let gateway = scenario.take_shared_by_id(gateway_id); + + let Gateway { + id, + operator: operator_result, + messages, + signers, + } = { gateway }; + + id.delete(); + assert!(operator == operator_result, 1); + messages.destroy_empty(); + + let ( + epoch, + mut epoch_by_signers_hash, + domain_separator_result, + minimum_rotation_delay_result, + last_rotation_timestamp, + previous_signers_retention_result, + ) = signers.destroy_for_testing(); + + let signer_epoch = epoch_by_signers_hash.remove(initial_signers.hash()); + epoch_by_signers_hash.destroy_empty(); + + assert!(epoch == 1, 2); + assert!(signer_epoch == 1, 3); + assert!(domain_separator == domain_separator_result, 4); + assert!(minimum_rotation_delay == minimum_rotation_delay_result, 5); + assert!(last_rotation_timestamp == timestamp, 6); + assert!(previous_signers_retention == previous_signers_retention_result, 7); + + clock.destroy_for_testing(); + scenario.end(); +} - assert!(is_message_approved( +#[test] +fun test_approve_message() { + let ctx = &mut sui::tx_context::dummy(); + + let message_id = std::ascii::string(b"Message Id"); + let channel = axelar_gateway::channel::new(ctx); + let source_chain = std::ascii::string(b"Source Chain"); + let source_address = std::ascii::string(b"Destination Address"); + let payload = vector[0, 1, 2, 3]; + let payload_hash = axelar_gateway::bytes32::new( + address::from_bytes(hash::keccak256(&payload)), + ); + + let message = message::new( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ); + + let mut gateway = dummy(ctx); + + approve_message(&mut gateway, &message); + // The second approve message should do nothing. + approve_message(&mut gateway, &message); + + assert!( + is_message_approved( &gateway, source_chain, message_id, source_address, channel.to_address(), payload_hash, - ) == true, 0); - - let approved_message = take_approved_message( - &mut gateway, - source_chain, - message_id, - source_address, - channel.to_address(), - payload - ); - - channel.consume_approved_message(approved_message); - - assert!(is_message_approved( + ) == + true, + 0, + ); + + let approved_message = take_approved_message( + &mut gateway, + source_chain, + message_id, + source_address, + channel.to_address(), + payload, + ); + + channel.consume_approved_message(approved_message); + + assert!( + is_message_approved( &gateway, source_chain, message_id, source_address, channel.to_address(), payload_hash, - ) == false, 1); + ) == + false, + 1, + ); - assert!(is_message_executed( + assert!( + is_message_executed( &gateway, source_chain, message_id, - ) == true, 2); + ) == + true, + 2, + ); - let MessageStatus { .. } = gateway.messages.remove(message.command_id()); + let MessageStatus { .. } = gateway.messages.remove(message.command_id()); - gateway.destroy_for_testing(); - channel.destroy(); - } - - #[test] - fun test_peel_messages() { - let message1 = message::new( - std::ascii::string(b"Source Chain 1"), - std::ascii::string(b"Message Id 1"), - std::ascii::string(b"Source Address 1"), - @0x1, - axelar_gateway::bytes32::new(@0x2), - ); - - let message2 = message::new( - std::ascii::string(b"Source Chain 2"), - std::ascii::string(b"Message Id 2"), - std::ascii::string(b"Source Address 2"), - @0x3, - axelar_gateway::bytes32::new(@0x4), - ); - - let bytes = bcs::to_bytes(&vector[message1, message2]); - - let messages = peel_messages(bytes); - - assert!(messages.length() == 2, 0); - assert!(messages[0] == message1, 1); - assert!(messages[1] == message2, 2); - } - - #[test] - #[expected_failure(abort_code = ERemainingData)] - fun test_peel_messages_no_remaining_data() { - let message1 = message::new( - std::ascii::string(b"Source Chain 1"), - std::ascii::string(b"Message Id 1"), - std::ascii::string(b"Source Address 1"), - @0x1, - axelar_gateway::bytes32::new(@0x2), - ); - - let mut bytes = bcs::to_bytes(&vector[message1]); - bytes.push_back(0); - - peel_messages(bytes); - } - - #[test] - #[expected_failure(abort_code = EInvalidLength)] - fun test_peel_messages_no_zero_messages() { - peel_messages(bcs::to_bytes(&vector[])); - } + gateway.destroy_for_testing(); + channel.destroy(); +} - #[test] - fun test_peel_weighted_signers() { - let signers = axelar_gateway::weighted_signers::dummy(); - let bytes = bcs::to_bytes(&signers); - let result = peel_weighted_signers(bytes); +#[test] +fun test_peel_messages() { + let message1 = message::new( + std::ascii::string(b"Source Chain 1"), + std::ascii::string(b"Message Id 1"), + std::ascii::string(b"Source Address 1"), + @0x1, + axelar_gateway::bytes32::new(@0x2), + ); + + let message2 = message::new( + std::ascii::string(b"Source Chain 2"), + std::ascii::string(b"Message Id 2"), + std::ascii::string(b"Source Address 2"), + @0x3, + axelar_gateway::bytes32::new(@0x4), + ); + + let bytes = bcs::to_bytes(&vector[message1, message2]); + + let messages = peel_messages(bytes); + + assert!(messages.length() == 2, 0); + assert!(messages[0] == message1, 1); + assert!(messages[1] == message2, 2); +} - assert!(result == signers, 0); - } +#[test] +#[expected_failure(abort_code = ERemainingData)] +fun test_peel_messages_no_remaining_data() { + let message1 = message::new( + std::ascii::string(b"Source Chain 1"), + std::ascii::string(b"Message Id 1"), + std::ascii::string(b"Source Address 1"), + @0x1, + axelar_gateway::bytes32::new(@0x2), + ); + + let mut bytes = bcs::to_bytes(&vector[message1]); + bytes.push_back(0); + + peel_messages(bytes); +} - #[test] - #[expected_failure(abort_code = ERemainingData)] - fun test_peel_weighted_signers_no_remaining_data() { - let signers = axelar_gateway::weighted_signers::dummy(); - let mut bytes = bcs::to_bytes(&signers); - bytes.push_back(0); +#[test] +#[expected_failure(abort_code = EInvalidLength)] +fun test_peel_messages_no_zero_messages() { + peel_messages(bcs::to_bytes(&vector[])); +} - peel_weighted_signers(bytes); - } +#[test] +fun test_peel_weighted_signers() { + let signers = axelar_gateway::weighted_signers::dummy(); + let bytes = bcs::to_bytes(&signers); + let result = peel_weighted_signers(bytes); + assert!(result == signers, 0); +} - #[test] - fun test_peel_proof() { - let proof = axelar_gateway::proof::dummy(); - let bytes = bcs::to_bytes(&proof); - let result = peel_proof(bytes); +#[test] +#[expected_failure(abort_code = ERemainingData)] +fun test_peel_weighted_signers_no_remaining_data() { + let signers = axelar_gateway::weighted_signers::dummy(); + let mut bytes = bcs::to_bytes(&signers); + bytes.push_back(0); - assert!(result == proof, 0); - } + peel_weighted_signers(bytes); +} - #[test] - #[expected_failure(abort_code = ERemainingData)] - fun test_peel_proof_no_remaining_data() { - let proof = axelar_gateway::proof::dummy(); - let mut bytes = bcs::to_bytes(&proof); - bytes.push_back(0); +#[test] +fun test_peel_proof() { + let proof = axelar_gateway::proof::dummy(); + let bytes = bcs::to_bytes(&proof); + let result = peel_proof(bytes); - peel_proof(bytes); - } + assert!(result == proof, 0); +} - #[test] - #[expected_failure(abort_code = EMessageNotApproved)] - fun test_take_approved_message_message_not_approved() { - let mut gateway = dummy(&mut sui::tx_context::dummy()); - - let message = message::new( - std::ascii::string(b"Source Chain"), - std::ascii::string(b"Message Id"), - std::ascii::string(b"Source Address"), - @0x1, - axelar_gateway::bytes32::new(@0x2), - ); +#[test] +#[expected_failure(abort_code = ERemainingData)] +fun test_peel_proof_no_remaining_data() { + let proof = axelar_gateway::proof::dummy(); + let mut bytes = bcs::to_bytes(&proof); + bytes.push_back(0); - gateway.messages.add(message.command_id(), MessageStatus { status: axelar_gateway::bytes32::new(@0x3)} ); + peel_proof(bytes); +} - let approved_message = take_approved_message( - &mut gateway, - std::ascii::string(b"Source Chain"), - std::ascii::string(b"Message Id"), - std::ascii::string(b"Source Address"), - @0x12, - vector[0, 1, 2], +#[test] +#[expected_failure(abort_code = EMessageNotApproved)] +fun test_take_approved_message_message_not_approved() { + let mut gateway = dummy(&mut sui::tx_context::dummy()); + + let message = message::new( + std::ascii::string(b"Source Chain"), + std::ascii::string(b"Message Id"), + std::ascii::string(b"Source Address"), + @0x1, + axelar_gateway::bytes32::new(@0x2), + ); + + gateway + .messages + .add( + message.command_id(), + MessageStatus { status: axelar_gateway::bytes32::new(@0x3) }, ); - let MessageStatus { .. } = gateway.messages.remove(message.command_id()); + let approved_message = take_approved_message( + &mut gateway, + std::ascii::string(b"Source Chain"), + std::ascii::string(b"Message Id"), + std::ascii::string(b"Source Address"), + @0x12, + vector[0, 1, 2], + ); - approved_message.destroy_for_testing(); - gateway.destroy_for_testing(); - } + let MessageStatus { .. } = gateway.messages.remove(message.command_id()); - #[test] - fun test_data_hash() { - let command_type = 5; - let data = vector[0, 1, 2, 3]; - let mut typed_data = vector::singleton(command_type); - typed_data.append(data); + approved_message.destroy_for_testing(); + gateway.destroy_for_testing(); +} - assert!(data_hash(command_type, data) == bytes32::from_bytes(hash::keccak256(&typed_data)), 0); - } +#[test] +fun test_data_hash() { + let command_type = 5; + let data = vector[0, 1, 2, 3]; + let mut typed_data = vector::singleton(command_type); + typed_data.append(data); + + assert!( + data_hash(command_type, data) == + bytes32::from_bytes(hash::keccak256(&typed_data)), + 0, + ); } 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 bc74f184..24641e20 100644 --- a/move/axelar_gateway/sources/types/proof.move +++ b/move/axelar_gateway/sources/types/proof.move @@ -1,103 +1,108 @@ -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, } +} - #[test_only] - public fun create_for_testing(signers: WeightedSigners, signatures: vector): Proof { - Proof { - signers, - signatures, - } +#[test_only] +public fun create_for_testing( + signers: WeightedSigners, + signatures: vector, +): Proof { + Proof { + signers, + signatures, } +} - #[test_only] - public fun dummy(): Proof { - let mut signature = sui::address::to_bytes(@0x01); - signature.append(sui::address::to_bytes(@0x23)); - signature.push_back(2); - Proof { - signers: axelar_gateway::weighted_signers::dummy(), - signatures: vector[ Signature { bytes: signature } ], - } +#[test_only] +public fun dummy(): Proof { + let mut signature = sui::address::to_bytes(@0x01); + signature.append(sui::address::to_bytes(@0x23)); + signature.push_back(2); + Proof { + signers: axelar_gateway::weighted_signers::dummy(), + signatures: vector[Signature { bytes: signature }], } } 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 a8b39443..d51926cd 100644 --- a/move/axelar_gateway/sources/types/weighted_signers.move +++ b/move/axelar_gateway/sources/types/weighted_signers.move @@ -1,89 +1,122 @@ -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; +/// ------ +/// Errors +/// ------ +/// Invalid length of the bytes +const EInvalidLength: u64 = 0; - /// ---------------- - /// Public Functions - /// ---------------- - public(package) fun signers(self: &WeightedSigners): vector { - self.signers - } +/// ---------------- +/// 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))) +} - #[test_only] - public fun create_for_testing( - signers: vector, - threshold: u128, - nonce: Bytes32, - ): WeightedSigners { - WeightedSigners { - signers, - threshold, - nonce, - } +#[test_only] +public fun create_for_testing( + signers: vector, + threshold: u128, + nonce: Bytes32, +): WeightedSigners { + WeightedSigners { + signers, + threshold, + nonce, } +} - #[test_only] - public fun dummy(): WeightedSigners { - let pub_key = vector[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]; - let signer = axelar_gateway::weighted_signer::new(pub_key, 123); - let nonce = bytes32::new(@3456); - let threshold = 100; - WeightedSigners { - signers: vector[signer], - threshold, - nonce, - } +#[test_only] +public fun dummy(): WeightedSigners { + let pub_key = vector[ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + ]; + let signer = axelar_gateway::weighted_signer::new(pub_key, 123); + let nonce = bytes32::new(@3456); + let threshold = 100; + WeightedSigners { + signers: vector[signer], + threshold, + nonce, } } diff --git a/move/example/Move.lock b/move/example/Move.lock index d2044fb7..55895567 100644 --- a/move/example/Move.lock +++ b/move/example/Move.lock @@ -39,6 +39,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.0" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" diff --git a/move/example/sources/gmp/gmp.move b/move/example/sources/gmp/gmp.move index 80d18f36..38641444 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"gmp"), - 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"gmp"), + 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/Move.lock b/move/gas_service/Move.lock index 8c34ebb5..69b0912c 100644 --- a/move/gas_service/Move.lock +++ b/move/gas_service/Move.lock @@ -21,6 +21,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" 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/Move.lock b/move/governance/Move.lock index 8ce09e0c..72bd1deb 100644 --- a/move/governance/Move.lock +++ b/move/governance/Move.lock @@ -39,6 +39,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" 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/Move.lock b/move/interchain_token/Move.lock index 1e5fbeb4..f442892e 100644 --- a/move/interchain_token/Move.lock +++ b/move/interchain_token/Move.lock @@ -21,6 +21,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" 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/Move.lock b/move/its/Move.lock index 5d41cb9d..cd0c31cb 100644 --- a/move/its/Move.lock +++ b/move/its/Move.lock @@ -49,6 +49,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" diff --git a/move/operators/Move.lock b/move/operators/Move.lock index e6817455..a9368ba3 100644 --- a/move/operators/Move.lock +++ b/move/operators/Move.lock @@ -21,6 +21,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" 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/Move.lock b/move/squid/Move.lock index ee5d5a05..a29ffcdf 100644 --- a/move/squid/Move.lock +++ b/move/squid/Move.lock @@ -69,6 +69,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.26.1" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" diff --git a/src/tx-builder.ts b/src/tx-builder.ts index 27897fd9..fbc55d23 100644 --- a/src/tx-builder.ts +++ b/src/tx-builder.ts @@ -248,9 +248,9 @@ export class TxBuilder { /** * Prepare a move build by creating a temporary directory to store the compiled move code - * @returns {dir: string, rm: () => void} - * - dir is the path to the temporary directory - * - rm is a function to remove the temporary directory + * @returns {tmpdir: string, rmTmpDir: () => void} + * - tmpdir is the path to the temporary directory + * - rmTmpDir is a function to remove the temporary directory */ private prepareMoveBuild() { const tmpdir = fs.mkdtempSync(`${__dirname}/.move-build-`); diff --git a/src/utils.ts b/src/utils.ts index a51005ae..54bb6b9a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import { execSync } from 'child_process'; import fs from 'fs'; import { getFullnodeUrl } from '@mysten/sui/client'; @@ -84,6 +85,22 @@ export function copyMovePackage(packageName: string, fromDir: null | string, toD fs.cpSync(`${fromDir}/${packageName}`, `${toDir}/${packageName}`, { recursive: true }); } +export const getInstalledSuiVersion = () => { + const suiVersion = execSync('sui --version').toString().trim(); + return parseVersion(suiVersion); +}; + +export const getDefinedSuiVersion = () => { + const version = fs.readFileSync(`${__dirname}/../version.json`, 'utf8'); + const suiVersion = JSON.parse(version).SUI_VERSION; + return parseVersion(suiVersion); +}; + +const parseVersion = (version: string) => { + const versionMatch = version.match(/\d+\.\d+\.\d+/); + return versionMatch?.[0]; +}; + export function parseEnv(arg: string) { switch (arg?.toLowerCase()) { case 'localnet': diff --git a/version.json b/version.json new file mode 100644 index 00000000..6eb6e087 --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "SUI_VERSION": "testnet-v1.32.0" +} \ No newline at end of file