diff --git a/.github/workflows/aptos.yml b/.github/workflows/aptos.yml new file mode 100644 index 0000000..498736f --- /dev/null +++ b/.github/workflows/aptos.yml @@ -0,0 +1,18 @@ +name: aptos + +on: + pull_request: + +jobs: + test: + name: Aptos Test + runs-on: ubuntu-latest + # https://github.com/aptos-labs/aptos-core/releases/tag/aptos-cli-v7.2.0 + # https://github.com/aptos-labs/aptos-core/commit/35102f5f33c69b8e48e030243a09edad80cbd946 + container: aptoslabs/tools:devnet_35102f5f33c69b8e48e030243a09edad80cbd946@sha256:06503b21b53ad904c7d689c47a78259b4891fa0d1c6932550c784bddc0d3cda0 + defaults: + run: + working-directory: aptos + steps: + - uses: actions/checkout@v4 + - run: sh ci.sh diff --git a/README.md b/README.md index 706a001..3b92a18 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Provide an execution framework and proof-of-concept (PoC) service for [Wormhole] - [x] [EVM](./evm/) - [x] [SVM](./svm/) -- [x] Sui Move -- [ ] Aptos Move +- [x] [Sui Move](./sui/) +- [x] [Aptos Move](./aptos/) ## Background diff --git a/aptos/.gitignore b/aptos/.gitignore new file mode 100644 index 0000000..b8dfbe4 --- /dev/null +++ b/aptos/.gitignore @@ -0,0 +1,4 @@ +*/.aptos +*/.coverage_map.mvcov +*/.trace +*/build diff --git a/aptos/DEPLOYMENTS.md b/aptos/DEPLOYMENTS.md new file mode 100644 index 0000000..af703ce --- /dev/null +++ b/aptos/DEPLOYMENTS.md @@ -0,0 +1,9 @@ +# Executor EVM Deployments + +## Testnet + +### April 24, 2025 + +Executor: [0x139717c339f08af674be77143507a905aa28cbc67a0e53e7095c07b630d73815](https://explorer.aptoslabs.com/account/0x139717c339f08af674be77143507a905aa28cbc67a0e53e7095c07b630d73815/modules/packages/executor?network=testnet) + +Executor Requests: [0xf6cc46a85f8cac9852c62904f09ec7fc9d3fe41ff646913853bed2a992c1d6d7](https://explorer.aptoslabs.com/account/0xf6cc46a85f8cac9852c62904f09ec7fc9d3fe41ff646913853bed2a992c1d6d7/modules/packages/executor_requests?network=testnet) diff --git a/aptos/README.md b/aptos/README.md new file mode 100644 index 0000000..583ea5a --- /dev/null +++ b/aptos/README.md @@ -0,0 +1,60 @@ +# Aptos + +The executor folder was generated with `aptos move init --name executor`. + +This module was developed with aptos CLI `7.2.0`. It should generally match the Sui implementation with minor changes necessary for Aptos-specific implementation details. + +> 💡 Note: The `payeeAddress` on the signed quote must be registered for `AptosCoin` before being able to receive payments. + +## Development + +[Move IDE Plugins](https://aptos.dev/en/build/smart-contracts#move-ide-plugins) + +### Compile + +```bash +aptos move compile --named-addresses executor=default +``` + +### Test + +```bash +aptos move test --named-addresses executor=default +``` + +For coverage, add the `--coverage` flag. + +```bash +aptos move test --coverage --named-addresses executor=default +``` + +### Deploy + +First initialize the config, setting the desired network and deployment private key. + +```bash +cd executor +aptos init +``` + +Then, publish the module immutably via a resource account. + + + +```bash +aptos move create-resource-account-and-publish-package --address-name executor --seed-encoding Utf8 --seed executorv1 +``` + + + +Repeat this with the `executor_requests` module. + + + +```bash +cd executor +aptos init +aptos move create-resource-account-and-publish-package --address-name executor_requests --named-addresses executor= --seed-encoding Utf8 --seed executor_requestsv1 +``` + + diff --git a/aptos/ci.sh b/aptos/ci.sh new file mode 100755 index 0000000..d367387 --- /dev/null +++ b/aptos/ci.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +cd executor +aptos init --skip-faucet +sh -c "aptos move test --coverage --named-addresses executor=default" +sh -c "aptos move coverage summary --named-addresses executor=default | grep \"Move Coverage: 100.00\"" +cd ../executor_requests +aptos init --skip-faucet +sh -c "aptos move test --coverage --named-addresses executor=default,executor_requests=default" +sh -c "aptos move coverage summary --named-addresses executor=default,executor_requests=default | grep \"Move Coverage: 100.00\"" +cd .. diff --git a/aptos/executor/.gitignore b/aptos/executor/.gitignore new file mode 100644 index 0000000..243f545 --- /dev/null +++ b/aptos/executor/.gitignore @@ -0,0 +1,2 @@ +.aptos/ +build/ \ No newline at end of file diff --git a/aptos/executor/Move.toml b/aptos/executor/Move.toml new file mode 100644 index 0000000..66bec44 --- /dev/null +++ b/aptos/executor/Move.toml @@ -0,0 +1,17 @@ +[package] +name = "executor" +version = "1.0.0" +license = "Apache 2.0" +authors = ["Wormhole Labs"] + +[addresses] +executor = "_" + +[dev-addresses] + +[dependencies.AptosFramework] +git = "https://github.com/aptos-labs/aptos-framework.git" +rev = "mainnet" +subdir = "aptos-framework" + +[dev-dependencies] diff --git a/aptos/executor/sources/executor.move b/aptos/executor/sources/executor.move new file mode 100644 index 0000000..52baf61 --- /dev/null +++ b/aptos/executor/sources/executor.move @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 + +module executor::executor { + use aptos_framework::aptos_coin::{AptosCoin}; + use aptos_framework::coin::{Self, Coin}; + use aptos_framework::event; + use aptos_std::from_bcs; + use executor::bytes; + use executor::cursor; + + const CHAIN_ID: u16 = 22; + + const E_QUOTE_SRC_CHAIN_MISMATCH: u64 = 0; + const E_QUOTE_DST_CHAIN_MISMATCH: u64 = 1; + const E_QUOTE_EXPIRED: u64 = 2; + + #[event] + struct RequestForExecution has drop, store { + quoter_address: vector, + amt_paid: u64, + dst_chain: u16, + dst_addr: address, + refund_addr: address, + signed_quote: vector, + request_bytes: vector, + relay_instructions: vector, + } + + public fun request_execution( + amount: Coin, + dst_chain: u16, + dst_addr: address, // akin to bytes32 + refund_addr: address, + signed_quote_bytes: vector, + request_bytes: vector, + relay_instructions: vector + ) { + let cursor = cursor::new(signed_quote_bytes); + bytes::take_bytes(&mut cursor, 4); // prefix + let quoter_address = bytes::take_bytes(&mut cursor, 20); + let payee_address = from_bcs::to_address(bytes::take_bytes(&mut cursor, 32)); + let quote_src_chain = bytes::take_u16_be(&mut cursor); + assert!(quote_src_chain == CHAIN_ID, E_QUOTE_SRC_CHAIN_MISMATCH); + let quote_dst_chain = bytes::take_u16_be(&mut cursor); + assert!(quote_dst_chain == dst_chain, E_QUOTE_DST_CHAIN_MISMATCH); + let expiry_time = bytes::take_u64_be(&mut cursor); + assert!(expiry_time > aptos_framework::timestamp::now_seconds(), E_QUOTE_EXPIRED); + cursor::take_rest(cursor); + let amt_paid = coin::value(&amount); + coin::deposit(payee_address, amount); + event::emit(RequestForExecution { + quoter_address, + amt_paid, + dst_chain, + dst_addr, + refund_addr, + signed_quote: signed_quote_bytes, + request_bytes, + relay_instructions + }); + } +} diff --git a/aptos/executor/sources/utils/bytes.move b/aptos/executor/sources/utils/bytes.move new file mode 100644 index 0000000..36f27a6 --- /dev/null +++ b/aptos/executor/sources/utils/bytes.move @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: Apache-2.0 +// Adapted from https://github.com/wormhole-foundation/wormhole/blob/cc9d8222858827afa9f4780164fcce32ba0dfb3a/sui/wormhole/sources/utils/bytes.move + +/// This module implements a library that serializes and deserializes specific +/// types into a buffer (i.e. `vector`). For serialization, the first +/// argument will be of `&mut vector`. For deserialization, the first +/// argument will be of `&mut Cursor` (see `executor::cursor` for more +/// details). +module executor::bytes { + use std::bcs::{Self}; + use std::vector; + use executor::cursor::{Self, Cursor}; + + public fun push_u8(buf: &mut vector, v: u8) { + vector::push_back(buf, v); + } + + public fun push_u16_be(buf: &mut vector, value: u16) { + push_reverse(buf, value); + } + + public fun push_u32_be(buf: &mut vector, value: u32) { + push_reverse(buf, value); + } + + public fun push_u64_be(buf: &mut vector, value: u64) { + push_reverse(buf, value); + } + + public fun push_u128_be(buf: &mut vector, value: u128) { + push_reverse(buf, value); + } + + public fun push_u256_be(buf: &mut vector, value: u256) { + push_reverse(buf, value); + } + + public fun take_u8(cur: &mut Cursor): u8 { + cursor::poke(cur) + } + + public fun take_u16_be(cur: &mut Cursor): u16 { + let out = 0; + let i = 0; + while (i < 2) { + out = (out << 8) + (cursor::poke(cur) as u16); + i = i + 1; + }; + out + } + + public fun take_u32_be(cur: &mut Cursor): u32 { + let out = 0; + let i = 0; + while (i < 4) { + out = (out << 8) + (cursor::poke(cur) as u32); + i = i + 1; + }; + out + } + + public fun take_u64_be(cur: &mut Cursor): u64 { + let out = 0; + let i =0; + while (i < 8) { + out = (out << 8) + (cursor::poke(cur) as u64); + i = i + 1; + }; + out + } + + public fun take_u128_be(cur: &mut Cursor): u128 { + let out = 0; + let i = 0; + while (i < 16) { + out = (out << 8) + (cursor::poke(cur) as u128); + i = i + 1; + }; + out + } + + public fun take_u256_be(cur: &mut Cursor): u256 { + let out = 0; + let i = 0; + while (i < 32) { + out = (out << 8) + (cursor::poke(cur) as u256); + i = i + 1; + }; + out + } + + public fun take_bytes(cur: &mut Cursor, num_bytes: u64): vector { + let out = vector::empty(); + let i = 0; + while (i < num_bytes) { + vector::push_back(&mut out, cursor::poke(cur)); + i = i + 1; + }; + out + } + + fun push_reverse(buf: &mut vector, v: T) { + let data = bcs::to_bytes(&v); + vector::reverse(&mut data); + vector::append(buf, data); + } +} + +#[test_only] +module executor::bytes_tests { + use std::vector; + use executor::bytes::{Self}; + use executor::cursor::{Self}; + + #[test] + fun test_push_u8(){ + let u = 0x12; + let s = vector::empty(); + bytes::push_u8(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u8(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u16_be(){ + let u = 0x1234; + let s = vector::empty(); + bytes::push_u16_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u16_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u32_be(){ + let u = 0x12345678; + let s = vector::empty(); + bytes::push_u32_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u32_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u64_be(){ + let u = 0x1234567812345678; + let s = vector::empty(); + bytes::push_u64_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u64_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u128_be(){ + let u = 0x12345678123456781234567812345678; + let s = vector::empty(); + bytes::push_u128_be(&mut s, u); + let cur = cursor::new(s); + let p = bytes::take_u128_be(&mut cur); + cursor::destroy_empty(cur); + assert!(p==u, 0); + } + + #[test] + fun test_push_u256_be(){ + let u = + 0x4738691759099793746170047375612500000000000000000000000000009876; + let s = vector::empty(); + bytes::push_u256_be(&mut s, u); + assert!( + s == x"4738691759099793746170047375612500000000000000000000000000009876", + 0 + ); + } + + #[test] + fun test_take_u8() { + let cursor = cursor::new(x"99"); + let byte = bytes::take_u8(&mut cursor); + assert!(byte==0x99, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u16_be() { + let cursor = cursor::new(x"9987"); + let u = bytes::take_u16_be(&mut cursor); + assert!(u == 0x9987, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u32_be() { + let cursor = cursor::new(x"99876543"); + let u = bytes::take_u32_be(&mut cursor); + assert!(u == 0x99876543, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u64_be() { + let cursor = cursor::new(x"1300000025000001"); + let u = bytes::take_u64_be(&mut cursor); + assert!(u == 0x1300000025000001, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_take_u128_be() { + let cursor = cursor::new(x"130209AB2500FA0113CD00AE25000001"); + let u = bytes::take_u128_be(&mut cursor); + assert!(u == 0x130209AB2500FA0113CD00AE25000001, 0); + cursor::destroy_empty(cursor); + } + + #[test] + fun test_to_bytes() { + let cursor = cursor::new(b"hello world"); + let hello = bytes::take_bytes(&mut cursor, 5); + bytes::take_u8(&mut cursor); + let world = bytes::take_bytes(&mut cursor, 5); + assert!(hello == b"hello", 0); + assert!(world == b"world", 0); + cursor::destroy_empty(cursor); + } + +} diff --git a/aptos/executor/sources/utils/cursor.move b/aptos/executor/sources/utils/cursor.move new file mode 100644 index 0000000..17b9f4e --- /dev/null +++ b/aptos/executor/sources/utils/cursor.move @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// Adapted from https://github.com/wormhole-foundation/wormhole/blob/cc9d8222858827afa9f4780164fcce32ba0dfb3a/sui/wormhole/sources/utils/cursor.move + +/// This module implements a custom type that allows consuming a vector +/// incrementally for parsing operations. It has no drop ability, and the only +/// way to deallocate it is by calling the `destroy_empty` method, which will +/// fail if the whole input hasn't been consumed. +/// +/// This setup statically guarantees that the parsing methods consume the full +/// input. +module executor::cursor { + use std::vector; + + /// Container for the underlying `vector` data to be consumed. + struct Cursor { + data: vector, + } + + /// Initialises a cursor from a vector. + public fun new(data: vector): Cursor { + // reverse the array so we have access to the first element easily + vector::reverse(&mut data); + Cursor { data } + } + + /// Retrieve underlying data. + public fun data(self: &Cursor): &vector { + &self.data + } + + /// Check whether the underlying data is empty. This method is useful for + /// iterating over a `Cursor` to exhaust its contents. + public fun is_empty(self: &Cursor): bool { + vector::is_empty(&self.data) + } + + /// Destroys an empty cursor. This method aborts if the cursor is not empty. + public fun destroy_empty(cursor: Cursor) { + let Cursor { data } = cursor; + vector::destroy_empty(data); + } + + /// Consumes the rest of the cursor (thus destroying it) and returns the + /// remaining bytes. + /// + /// NOTE: Only use this function if you intend to consume the rest of the + /// bytes. Since the result is a vector, which can be dropped, it is not + /// possible to statically guarantee that the rest will be used. + public fun take_rest(cursor: Cursor): vector { + let Cursor { data } = cursor; + // Because the data was reversed in initialization, we need to reverse + // again so it is in the same order as the original input. + vector::reverse(&mut data); + data + } + + /// Retrieve the first element of the cursor and advances it. + public fun poke(self: &mut Cursor): T { + vector::pop_back(&mut self.data) + } +} diff --git a/aptos/executor/tests/executor_tests.move b/aptos/executor/tests/executor_tests.move new file mode 100644 index 0000000..6481051 --- /dev/null +++ b/aptos/executor/tests/executor_tests.move @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module executor::executor_tests { + use executor::executor; + use aptos_framework::aptos_coin::{Self, AptosCoin}; + use aptos_framework::coin; + use aptos_framework::event; + use std::signer; + use std::vector; + + #[test(aptos_framework = @aptos_framework, expected_payee = @0x000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a)] + fun test_executor(aptos_framework: &signer, expected_payee: &signer) { + let user = @0xCAFE; + aptos_framework::timestamp::set_time_has_started_for_testing(aptos_framework); + let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework); + let amount = coin::mint(100, &mint_cap); + // The payee account must exist to be registered for a coin + std::account::create_account_for_test(signer::address_of(expected_payee)); + // The payee must be registered for the AptosCoin in order to receive a deposit + coin::register(expected_payee); + assert!(coin::balance(signer::address_of(expected_payee)) == 0, 0); + executor::request_execution( + amount, + 6, + @0x1234567891234567891234567891234512345678912345678912345678912345, + user, + x"455130315241c9276698439fef2780dbab76fec90b633fbd000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a001600060000000067dc8c0a00000000000003e800000000000000020000120430544c000000002bb3cab500199f0dc83362b168ade5aa32a99747a0d0b4d7c7d7f9acdee2c62b89de7661dc6968d845bf9c0997553f6f9c0418ba3cee97787eb9c19266754892124c8c65751b", + x"45524e312712000000000000000000000000e2a90da727f328e2324536fe2b4837f6c77dda7d0000000000000000000000000000000000000000000000000000000000000006", + x"0100000000000000000000000000061a8000000000000000000000000000000000" + ); + let effects = event::emitted_events(); + assert!(vector::length(&effects) == 1, 0); + assert!(coin::balance(signer::address_of(expected_payee)) == 100, 0); + coin::destroy_mint_cap(mint_cap); + coin::destroy_burn_cap(burn_cap); + } + + #[test(aptos_framework = @aptos_framework)] + #[expected_failure] + fun test_executor_fail_with_invalid_quote_header(aptos_framework: &signer) { + let user = @0xCAFE; + let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework); + executor::request_execution( + coin::mint(100, &mint_cap), + 6, + @0x1234567891234567891234567891234512345678912345678912345678912345, + user, + x"455130315241c9276698439fef2780dbab76fec90b633fbd000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a001600060000000067dc8c", + x"45524e312712000000000000000000000000e2a90da727f328e2324536fe2b4837f6c77dda7d0000000000000000000000000000000000000000000000000000000000000006", + x"0100000000000000000000000000061a8000000000000000000000000000000000" + ); + coin::destroy_mint_cap(mint_cap); + coin::destroy_burn_cap(burn_cap); + } + + #[test(aptos_framework = @aptos_framework)] + #[expected_failure(abort_code = executor::E_QUOTE_SRC_CHAIN_MISMATCH)] + fun test_executor_fail_with_invalid_source_chain(aptos_framework: &signer) { + let user = @0xCAFE; + let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework); + executor::request_execution( + coin::mint(100, &mint_cap), + 6, + @0x1234567891234567891234567891234512345678912345678912345678912345, + user, + x"455130315241c9276698439fef2780dbab76fec90b633fbd000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a000600060000000067dc8c0a00000000000003e800000000000000020000120430544c000000002bb3cab500199f0dc83362b168ade5aa32a99747a0d0b4d7c7d7f9acdee2c62b89de7661dc6968d845bf9c0997553f6f9c0418ba3cee97787eb9c19266754892124c8c65751b", + x"45524e312712000000000000000000000000e2a90da727f328e2324536fe2b4837f6c77dda7d0000000000000000000000000000000000000000000000000000000000000006", + x"0100000000000000000000000000061a8000000000000000000000000000000000" + ); + coin::destroy_mint_cap(mint_cap); + coin::destroy_burn_cap(burn_cap); + } + + #[test(aptos_framework = @aptos_framework)] + #[expected_failure(abort_code = executor::E_QUOTE_DST_CHAIN_MISMATCH)] + fun test_executor_fail_with_invalid_destination_chain(aptos_framework: &signer) { + let user = @0xCAFE; + let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework); + executor::request_execution( + coin::mint(100, &mint_cap), + 10002, + @0x1234567891234567891234567891234512345678912345678912345678912345, + user, + x"455130315241c9276698439fef2780dbab76fec90b633fbd000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a001600060000000067dc8c0a00000000000003e800000000000000020000120430544c000000002bb3cab500199f0dc83362b168ade5aa32a99747a0d0b4d7c7d7f9acdee2c62b89de7661dc6968d845bf9c0997553f6f9c0418ba3cee97787eb9c19266754892124c8c65751b", + x"45524e312712000000000000000000000000e2a90da727f328e2324536fe2b4837f6c77dda7d0000000000000000000000000000000000000000000000000000000000000006", + x"0100000000000000000000000000061a8000000000000000000000000000000000" + ); + coin::destroy_mint_cap(mint_cap); + coin::destroy_burn_cap(burn_cap); + } + + #[test(aptos_framework = @aptos_framework)] + #[expected_failure(abort_code = executor::E_QUOTE_EXPIRED)] + fun test_executor_fail_with_expired_quote(aptos_framework: &signer) { + let user = @0xCAFE; + aptos_framework::timestamp::set_time_has_started_for_testing(aptos_framework); + aptos_framework::timestamp::update_global_time_for_test_secs(1742507018); + let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework); + executor::request_execution( + coin::mint(100, &mint_cap), + 6, + @0x1234567891234567891234567891234512345678912345678912345678912345, + user, + x"455130315241c9276698439fef2780dbab76fec90b633fbd000000000000000000000000f7122c001b3e07d7fafd8be3670545135859954a001600060000000067dc8c0a00000000000003e800000000000000020000120430544c000000002bb3cab500199f0dc83362b168ade5aa32a99747a0d0b4d7c7d7f9acdee2c62b89de7661dc6968d845bf9c0997553f6f9c0418ba3cee97787eb9c19266754892124c8c65751b", + x"45524e312712000000000000000000000000e2a90da727f328e2324536fe2b4837f6c77dda7d0000000000000000000000000000000000000000000000000000000000000006", + x"0100000000000000000000000000061a8000000000000000000000000000000000" + ); + coin::destroy_mint_cap(mint_cap); + coin::destroy_burn_cap(burn_cap); + } + +} diff --git a/aptos/executor_requests/.gitignore b/aptos/executor_requests/.gitignore new file mode 100644 index 0000000..243f545 --- /dev/null +++ b/aptos/executor_requests/.gitignore @@ -0,0 +1,2 @@ +.aptos/ +build/ \ No newline at end of file diff --git a/aptos/executor_requests/Move.toml b/aptos/executor_requests/Move.toml new file mode 100644 index 0000000..eba6b5b --- /dev/null +++ b/aptos/executor_requests/Move.toml @@ -0,0 +1,20 @@ +[package] +name = "executor_requests" +version = "1.0.0" +license = "Apache 2.0" +authors = ["Wormhole Labs"] + +[dependencies.executor] +local = "../executor" + +[addresses] +executor_requests = "_" + +[dev-addresses] + +[dependencies.AptosFramework] +git = "https://github.com/aptos-labs/aptos-framework.git" +rev = "mainnet" +subdir = "aptos-framework" + +[dev-dependencies] diff --git a/aptos/executor_requests/sources/executor_requests.move b/aptos/executor_requests/sources/executor_requests.move new file mode 100644 index 0000000..4a97cb8 --- /dev/null +++ b/aptos/executor_requests/sources/executor_requests.move @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 + +module executor_requests::executor_requests { + use std::vector; + use executor::bytes; + + const REQ_VAA_V1: vector = b"ERV1"; + const REQ_CCTP_V1: vector = b"ERC1"; + + const E_INVALID_VEC_LENGTH: u64 = 0; + + public fun make_vaa_v1_request( + emitter_chain: u16, + emitter_address: vector, + sequence: u64 + ): vector { + assert!(emitter_address.length() == 32,E_INVALID_VEC_LENGTH); + let ret = vector::empty(); + ret.append(REQ_VAA_V1); + bytes::push_u16_be(&mut ret, emitter_chain); + ret.append(emitter_address); + bytes::push_u64_be(&mut ret, sequence); + ret + } + + public fun make_cctp_v1_request( + src_domain: u32, + nonce: u64, + ): vector { + let ret = vector::empty(); + ret.append(REQ_CCTP_V1); + bytes::push_u32_be(&mut ret, src_domain); + bytes::push_u64_be(&mut ret, nonce); + ret + } +} diff --git a/aptos/executor_requests/tests/executor_requests_tests.move b/aptos/executor_requests/tests/executor_requests_tests.move new file mode 100644 index 0000000..d4d3f83 --- /dev/null +++ b/aptos/executor_requests/tests/executor_requests_tests.move @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module executor_requests::executor_requests_tests { + use executor_requests::executor_requests; + + #[test] + fun test_make_vaa_v1_request() { + let res = executor_requests::make_vaa_v1_request( + 10002, + x"000000000000000000000000d4a6a72a025599fd7357c0f157c718d0f5e38c76", + 29 + ); + assert!(res == x"455256312712000000000000000000000000d4a6a72a025599fd7357c0f157c718d0f5e38c76000000000000001d", 0); + } + + #[test] + fun test_make_cctp_v1_request() { + let res = executor_requests::make_cctp_v1_request( + 6, + 6344 + ); + assert!(res == x"455243310000000600000000000018c8", 0); + } + + #[test] + #[expected_failure(abort_code = executor_requests::E_INVALID_VEC_LENGTH)] + fun test_make_vaa_v1_request_fail_with_emitter_too_short() { + executor_requests::make_vaa_v1_request( + 10002, + x"000000000000000000000000d4a6a72a025599fd7357c0f157c718d0f5e38c", + 29 + ); + } + + #[test] + #[expected_failure(abort_code = executor_requests::E_INVALID_VEC_LENGTH)] + fun test_make_vaa_v1_request_fail_with_emitter_too_long() { + executor_requests::make_vaa_v1_request( + 10002, + x"000000000000000000000000d4a6a72a025599fd7357c0f157c718d0f5e38c7600", + 29 + ); + } + +}