From 1608b155d9db6c913d8d99284621437681d7d35b Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 15 Nov 2024 17:19:32 +0200 Subject: [PATCH 01/26] make event for interchain transfer consistent with EVM --- move/its/sources/events.move | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 8bbb7231..953836e1 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -65,7 +65,11 @@ public(package) fun interchain_transfer( amount: u64, data: &vector, ) { - let data_hash = bytes32::new(address::from_bytes(keccak256(data))); + let data_hash = if (data.length() == 0) { + bytes32::new(@0x0) + } else { + bytes32::new(address::from_bytes(keccak256(data))) + }; event::emit(InterchainTransfer { token_id, source_address, From bf905145b9235e92c35bc7a493571cb49960aaa2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 19 Nov 2024 17:05:45 +0200 Subject: [PATCH 02/26] added some event tests and added an event emission on receive_deploy_interchain_token --- move/its/sources/events.move | 45 ++++++++++++++++++++++++++ move/its/sources/its.move | 8 ++++- move/its/sources/versioned/its_v0.move | 8 ++--- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 953836e1..04ca6fd8 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -128,3 +128,48 @@ public(package) fun unregistered_coin_received( decimals, }); } + +// --------- +// Test Only +// --------- +use its::coin::COIN; +use its::token_id; + +// ----- +// Tests +// ----- +#[test] +fun test_interchain_transfer() { + let token_id = token_id::from_address(@0x1); + let source_address = @0x2; + let destination_chain = b"destination chain".to_ascii_string(); + let destination_address = b"destination address"; + let amount = 123; + let data1 = b""; + let data1_hash = bytes32::new(@0x0); + let data2 = b"data"; + let data2_hash = bytes32::new(address::from_bytes(keccak256(&data2))); + + interchain_transfer( + token_id, + source_address, + destination_chain, + destination_address, + amount, + &data1, + ); + interchain_transfer( + token_id, + source_address, + destination_chain, + destination_address, + amount, + &data2, + ); + let events = event::events_by_type>(); + + assert!(events.length() == 2); + + assert!(events[0].data_hash == data1_hash); + assert!(events[1].data_hash == data2_hash); +} diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 5c15f18b..99455494 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -417,6 +417,7 @@ fun test_register_coin() { let coin_management = its::coin_management::new_locked(); register_coin(&mut its, coin_info, coin_management); + assert!(sui::event::events_by_type>().length() == 1); sui::test_utils::destroy(its); } @@ -443,6 +444,7 @@ fun test_deploy_remote_interchain_token() { token_id, destination_chain, ); + assert!(sui::event::events_by_type>().length() == 1); let mut writer = abi::new_writer(6); @@ -481,6 +483,7 @@ fun test_deploy_interchain_token() { let coin_management = its::coin_management::new_locked(); let token_id = register_coin(&mut its, coin_info, coin_management); + assert!(sui::event::events_by_type>().length() == 1); let amount = 1234; let coin = sui::coin::mint_for_testing(amount, ctx); let destination_chain = ascii::string(b"Chain Name"); @@ -502,6 +505,7 @@ fun test_deploy_interchain_token() { interchain_transfer_ticket, &clock, ); + assert!(sui::event::events_by_type>().length() == 1); let mut writer = abi::new_writer(6); writer @@ -571,6 +575,7 @@ fun test_receive_interchain_transfer() { ); receive_interchain_transfer(&mut its, approved_message, &clock, ctx); + assert!(sui::event::events_by_type>().length() == 1); clock.destroy_for_testing(); sui::test_utils::destroy(its); @@ -631,6 +636,7 @@ fun test_receive_interchain_transfer_with_data() { &clock, ctx, ); + assert!(sui::event::events_by_type>().length() == 1); assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); @@ -678,7 +684,7 @@ fun test_receive_deploy_interchain_token() { ); receive_deploy_interchain_token(&mut its, approved_message); - + assert!(sui::event::events_by_type>().length() == 1); clock.destroy_for_testing(); sui::test_utils::destroy(its); } diff --git a/move/its/sources/versioned/its_v0.move b/move/its/sources/versioned/its_v0.move index e516fd1d..add21a9e 100644 --- a/move/its/sources/versioned/its_v0.move +++ b/move/its/sources/versioned/its_v0.move @@ -206,10 +206,6 @@ public(package) fun register_coin( self.add_registered_coin(token_id, coin_management, coin_info); - events::coin_registered( - token_id, - ); - token_id } @@ -589,6 +585,10 @@ fun add_registered_coin( let type_name = type_name::get(); add_registered_coin_type(self, token_id, type_name); + + events::coin_registered( + token_id, + ); } /// Send a payload to a destination chain. The destination chain needs to have a From 3c48734cf5163d52fbe6239be8f8138624254a66 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 19 Nov 2024 17:48:17 +0200 Subject: [PATCH 03/26] add #[test-only] to only test inclusions --- move/its/sources/events.move | 2 ++ 1 file changed, 2 insertions(+) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 04ca6fd8..cad04dd4 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -132,7 +132,9 @@ public(package) fun unregistered_coin_received( // --------- // Test Only // --------- +#[test-only] use its::coin::COIN; +#[test-only] use its::token_id; // ----- From faa0ae7fbc90869e8fceb864ff223ae703dfe1e6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 19 Nov 2024 17:50:39 +0200 Subject: [PATCH 04/26] fix test only --- move/its/sources/events.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index cad04dd4..77040b0f 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -132,9 +132,9 @@ public(package) fun unregistered_coin_received( // --------- // Test Only // --------- -#[test-only] +#[test_only] use its::coin::COIN; -#[test-only] +#[test_only] use its::token_id; // ----- From f733742736e261b2a6be751c910a8d012dce69cc Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 14:13:38 +0200 Subject: [PATCH 05/26] added event finding in utils --- move/its/sources/events.move | 6 +++--- move/its/sources/its.move | 25 ++++++++++++++++++------- move/utils/sources/utils/utils.move | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 77040b0f..26a6ff8c 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -136,6 +136,8 @@ public(package) fun unregistered_coin_received( use its::coin::COIN; #[test_only] use its::token_id; +#[test_only] +use utils::utils; // ----- // Tests @@ -168,9 +170,7 @@ fun test_interchain_transfer() { amount, &data2, ); - let events = event::events_by_type>(); - - assert!(events.length() == 2); + let events = utils::multiple_events>(2); assert!(events[0].data_hash == data1_hash); assert!(events[1].data_hash == data2_hash); diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 99455494..1fc652dd 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -323,6 +323,8 @@ use axelar_gateway::channel; use std::string; #[test_only] use abi::abi; +#[test_only] +use utils::utils; // === MESSAGE TYPES === #[test_only] @@ -417,7 +419,7 @@ fun test_register_coin() { let coin_management = its::coin_management::new_locked(); register_coin(&mut its, coin_info, coin_management); - assert!(sui::event::events_by_type>().length() == 1); + utils::single_event>(); sui::test_utils::destroy(its); } @@ -444,7 +446,8 @@ fun test_deploy_remote_interchain_token() { token_id, destination_chain, ); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); let mut writer = abi::new_writer(6); @@ -483,7 +486,9 @@ fun test_deploy_interchain_token() { let coin_management = its::coin_management::new_locked(); let token_id = register_coin(&mut its, coin_info, coin_management); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); + let amount = 1234; let coin = sui::coin::mint_for_testing(amount, ctx); let destination_chain = ascii::string(b"Chain Name"); @@ -505,7 +510,9 @@ fun test_deploy_interchain_token() { interchain_transfer_ticket, &clock, ); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); + let mut writer = abi::new_writer(6); writer @@ -575,7 +582,8 @@ fun test_receive_interchain_transfer() { ); receive_interchain_transfer(&mut its, approved_message, &clock, ctx); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); @@ -636,7 +644,8 @@ fun test_receive_interchain_transfer_with_data() { &clock, ctx, ); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); @@ -684,7 +693,9 @@ fun test_receive_deploy_interchain_token() { ); receive_deploy_interchain_token(&mut its, approved_message); - assert!(sui::event::events_by_type>().length() == 1); + + utils::single_event>(); + clock.destroy_for_testing(); sui::test_utils::destroy(its); } diff --git a/move/utils/sources/utils/utils.move b/move/utils/sources/utils/utils.move index 57cd6edc..979468f1 100644 --- a/move/utils/sources/utils/utils.move +++ b/move/utils/sources/utils/utils.move @@ -29,6 +29,22 @@ public macro fun peel<$T>($data: vector, $peel_fn: |&mut BCS| -> $T): $T { result } +#[test_only] +use sui::event; + +#[test_only] +public fun multiple_events(n: u64): vector { + let events = event::events_by_type(); + assert!(events.length() == n); + events +} + +#[test_only] +public fun single_event(): T { + let events = multiple_events(1); + events[0] +} + #[test] fun peel_bcs_data_succeeds() { let test_bytes = b"test"; From adc67e5f1e8b90149c6db42b2bd896d1e7d64e18 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 14:43:24 +0200 Subject: [PATCH 06/26] added separate tests for empty and non-empty and tested more args --- move/its/sources/events.move | 41 +++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 26a6ff8c..962e1af7 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -143,16 +143,14 @@ use utils::utils; // Tests // ----- #[test] -fun test_interchain_transfer() { +fun test_interchain_transfer_empty_data() { let token_id = token_id::from_address(@0x1); let source_address = @0x2; let destination_chain = b"destination chain".to_ascii_string(); let destination_address = b"destination address"; let amount = 123; - let data1 = b""; - let data1_hash = bytes32::new(@0x0); - let data2 = b"data"; - let data2_hash = bytes32::new(address::from_bytes(keccak256(&data2))); + let data = b""; + let data_hash = bytes32::new(@0x0); interchain_transfer( token_id, @@ -160,18 +158,41 @@ fun test_interchain_transfer() { destination_chain, destination_address, amount, - &data1, + &data, ); + let event = utils::single_event>(); + + assert!(event.data_hash == data_hash); + assert!(event.source_address == source_address); + assert!(event.destination_chain == destination_chain); + assert!(event.destination_address == destination_address); + assert!(event.amount == amount); +} + + +#[test] +fun test_interchain_transfer_nonempty_data() { + let token_id = token_id::from_address(@0x1); + let source_address = @0x2; + let destination_chain = b"destination chain".to_ascii_string(); + let destination_address = b"destination address"; + let amount = 123; + let data = b"data"; + let data_hash = bytes32::new(address::from_bytes(keccak256(&data))); + interchain_transfer( token_id, source_address, destination_chain, destination_address, amount, - &data2, + &data, ); - let events = utils::multiple_events>(2); + let event = utils::single_event>(); - assert!(events[0].data_hash == data1_hash); - assert!(events[1].data_hash == data2_hash); + assert!(event.data_hash == data_hash); + assert!(event.source_address == source_address); + assert!(event.destination_chain == destination_chain); + assert!(event.destination_address == destination_address); + assert!(event.amount == amount); } From 94dac9afe92f248ef1bd97564aa0ea72faf8637d Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 15:40:47 +0200 Subject: [PATCH 07/26] checking for event in the gateway --- move/axelar_gateway/sources/channel.move | 7 ++++++- move/axelar_gateway/sources/gateway.move | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 133a576d..3ccacf09 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -126,6 +126,9 @@ public(package) fun create_approved_message( // --------- // Test Only // --------- +#[test_only] +use utils::utils; + #[test_only] public fun new_approved_message( source_chain: String, @@ -196,7 +199,9 @@ public(package) fun approved_message_payload( fun test_new_and_destroy() { let ctx = &mut sui::tx_context::dummy(); let channel: Channel = new(ctx); - channel.destroy() + utils::single_event(); + channel.destroy(); + utils::single_event(); } #[test] diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 2a123aba..2006d1d6 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -285,6 +285,8 @@ fun version_control(): VersionControl { use sui::bcs; #[test_only] use axelar_gateway::auth::generate_proof; +#[test_only] +use axelar_gateway::events; #[test_only] public fun create_for_testing( @@ -600,6 +602,9 @@ fun test_take_approved_message() { destination_id, payload, ); + + utils::single_event(); + let expected_approved_message = axelar_gateway::channel::create_approved_message( source_chain, message_id, @@ -661,6 +666,8 @@ fun test_approve_messages() { self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); + utils::single_event(); + clock.destroy_for_testing(); sui::test_utils::destroy(self) } @@ -760,6 +767,7 @@ fun test_rotate_signers() { &clock, ctx, ); + utils::single_event(); let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( @@ -777,6 +785,8 @@ fun test_rotate_signers() { ctx, ); + utils::multiple_events(2); + clock.destroy_for_testing(); sui::test_utils::destroy(self); } @@ -1041,6 +1051,7 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); + utils::single_event(); sui::test_utils::destroy(gateway); channel.destroy(); } From f874c105e1eec1fdcfe8ea2ea5f251c15d5dd567 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 15:47:11 +0200 Subject: [PATCH 08/26] Revert "checking for event in the gateway" This reverts commit 94dac9afe92f248ef1bd97564aa0ea72faf8637d. --- move/axelar_gateway/sources/channel.move | 7 +------ move/axelar_gateway/sources/gateway.move | 11 ----------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 3ccacf09..133a576d 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -126,9 +126,6 @@ public(package) fun create_approved_message( // --------- // Test Only // --------- -#[test_only] -use utils::utils; - #[test_only] public fun new_approved_message( source_chain: String, @@ -199,9 +196,7 @@ public(package) fun approved_message_payload( fun test_new_and_destroy() { let ctx = &mut sui::tx_context::dummy(); let channel: Channel = new(ctx); - utils::single_event(); - channel.destroy(); - utils::single_event(); + channel.destroy() } #[test] diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 2006d1d6..2a123aba 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -285,8 +285,6 @@ fun version_control(): VersionControl { use sui::bcs; #[test_only] use axelar_gateway::auth::generate_proof; -#[test_only] -use axelar_gateway::events; #[test_only] public fun create_for_testing( @@ -602,9 +600,6 @@ fun test_take_approved_message() { destination_id, payload, ); - - utils::single_event(); - let expected_approved_message = axelar_gateway::channel::create_approved_message( source_chain, message_id, @@ -666,8 +661,6 @@ fun test_approve_messages() { self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); - utils::single_event(); - clock.destroy_for_testing(); sui::test_utils::destroy(self) } @@ -767,7 +760,6 @@ fun test_rotate_signers() { &clock, ctx, ); - utils::single_event(); let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( @@ -785,8 +777,6 @@ fun test_rotate_signers() { ctx, ); - utils::multiple_events(2); - clock.destroy_for_testing(); sui::test_utils::destroy(self); } @@ -1051,7 +1041,6 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); - utils::single_event(); sui::test_utils::destroy(gateway); channel.destroy(); } From 8f716bc9e424864cbcf22a3d04f6bdc58a390af1 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 15:52:50 +0200 Subject: [PATCH 09/26] check for events --- move/axelar_gateway/sources/channel.move | 7 ++++++- move/axelar_gateway/sources/gateway.move | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 133a576d..3ccacf09 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -126,6 +126,9 @@ public(package) fun create_approved_message( // --------- // Test Only // --------- +#[test_only] +use utils::utils; + #[test_only] public fun new_approved_message( source_chain: String, @@ -196,7 +199,9 @@ public(package) fun approved_message_payload( fun test_new_and_destroy() { let ctx = &mut sui::tx_context::dummy(); let channel: Channel = new(ctx); - channel.destroy() + utils::single_event(); + channel.destroy(); + utils::single_event(); } #[test] diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 2a123aba..9f414747 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -285,6 +285,8 @@ fun version_control(): VersionControl { use sui::bcs; #[test_only] use axelar_gateway::auth::generate_proof; +#[test_only] +use axelar_gateway::events; #[test_only] public fun create_for_testing( @@ -600,6 +602,9 @@ fun test_take_approved_message() { destination_id, payload, ); + + utils::single_event(); + let expected_approved_message = axelar_gateway::channel::create_approved_message( source_chain, message_id, @@ -661,6 +666,8 @@ fun test_approve_messages() { self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); + utils::single_event(); + clock.destroy_for_testing(); sui::test_utils::destroy(self) } @@ -761,6 +768,8 @@ fun test_rotate_signers() { ctx, ); + utils::single_event(); + let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( data_hash, @@ -777,6 +786,8 @@ fun test_rotate_signers() { ctx, ); + utils::multiple_events(2); + clock.destroy_for_testing(); sui::test_utils::destroy(self); } @@ -1041,6 +1052,9 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); + + utils::single_event(); + sui::test_utils::destroy(gateway); channel.destroy(); } From b77670a99a9075d46218b08b36af35eb753c348d Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 16:39:29 +0200 Subject: [PATCH 10/26] some renaming --- move/its/sources/events.move | 4 ++-- move/its/sources/its.move | 14 +++++++------- move/utils/sources/utils/utils.move | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 10f56cdf..c66a22b8 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -202,7 +202,7 @@ fun test_interchain_transfer_empty_data() { amount, &data, ); - let event = utils::single_event>(); + let event = utils::assert_single_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); @@ -230,7 +230,7 @@ fun test_interchain_transfer_nonempty_data() { amount, &data, ); - let event = utils::single_event>(); + let event = utils::assert_single_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); diff --git a/move/its/sources/its.move b/move/its/sources/its.move index ce92d55f..96fd7cb9 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -435,7 +435,7 @@ fun test_register_coin() { let coin_management = its::coin_management::new_locked(); register_coin(&mut its, coin_info, coin_management); - utils::single_event>(); + utils::assert_single_event>(); sui::test_utils::destroy(its); } @@ -463,7 +463,7 @@ fun test_deploy_remote_interchain_token() { destination_chain, ); - utils::single_event>(); + utils::assert_single_event>(); let mut writer = abi::new_writer(6); @@ -503,7 +503,7 @@ fun test_deploy_interchain_token() { let token_id = register_coin(&mut its, coin_info, coin_management); - utils::single_event>(); + utils::assert_single_event>(); let amount = 1234; let coin = sui::coin::mint_for_testing(amount, ctx); @@ -527,7 +527,7 @@ fun test_deploy_interchain_token() { &clock, ); - utils::single_event>(); + utils::assert_single_event>(); let mut writer = abi::new_writer(6); @@ -599,7 +599,7 @@ fun test_receive_interchain_transfer() { receive_interchain_transfer(&mut its, approved_message, &clock, ctx); - utils::single_event>(); + utils::assert_single_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); @@ -661,7 +661,7 @@ fun test_receive_interchain_transfer_with_data() { ctx, ); - utils::single_event>(); + utils::assert_single_event>(); assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); @@ -710,7 +710,7 @@ fun test_receive_deploy_interchain_token() { receive_deploy_interchain_token(&mut its, approved_message); - utils::single_event>(); + utils::assert_single_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); diff --git a/move/utils/sources/utils/utils.move b/move/utils/sources/utils/utils.move index 979468f1..206fe24f 100644 --- a/move/utils/sources/utils/utils.move +++ b/move/utils/sources/utils/utils.move @@ -33,15 +33,15 @@ public macro fun peel<$T>($data: vector, $peel_fn: |&mut BCS| -> $T): $T { use sui::event; #[test_only] -public fun multiple_events(n: u64): vector { +public fun assert_events(n: u64): vector { let events = event::events_by_type(); assert!(events.length() == n); events } #[test_only] -public fun single_event(): T { - let events = multiple_events(1); +public fun assert_single_event(): T { + let events = assert_events(1); events[0] } From 275232d939c9b71fa0cac79779d558a3702bc0a5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 16:45:42 +0200 Subject: [PATCH 11/26] fix naming issue --- move/axelar_gateway/sources/channel.move | 4 ++-- move/axelar_gateway/sources/gateway.move | 10 +++++----- move/its/sources/events.move | 4 ++-- move/its/sources/its.move | 14 +++++++------- move/utils/sources/utils/utils.move | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index 3ccacf09..df2cdecc 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -199,9 +199,9 @@ public(package) fun approved_message_payload( fun test_new_and_destroy() { let ctx = &mut sui::tx_context::dummy(); let channel: Channel = new(ctx); - utils::single_event(); + utils::assert_single_event(); channel.destroy(); - utils::single_event(); + utils::assert_single_event(); } #[test] diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 9f414747..b97b9c1d 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -603,7 +603,7 @@ fun test_take_approved_message() { payload, ); - utils::single_event(); + utils::assert_single_event(); let expected_approved_message = axelar_gateway::channel::create_approved_message( source_chain, @@ -666,7 +666,7 @@ fun test_approve_messages() { self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); - utils::single_event(); + utils::assert_single_event(); clock.destroy_for_testing(); sui::test_utils::destroy(self) @@ -768,7 +768,7 @@ fun test_rotate_signers() { ctx, ); - utils::single_event(); + utils::assert_single_event(); let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( @@ -786,7 +786,7 @@ fun test_rotate_signers() { ctx, ); - utils::multiple_events(2); + utils::assert_events(2); clock.destroy_for_testing(); sui::test_utils::destroy(self); @@ -1053,7 +1053,7 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); - utils::single_event(); + utils::assert_single_event(); sui::test_utils::destroy(gateway); channel.destroy(); diff --git a/move/its/sources/events.move b/move/its/sources/events.move index c66a22b8..13bd44eb 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -202,7 +202,7 @@ fun test_interchain_transfer_empty_data() { amount, &data, ); - let event = utils::assert_single_event>(); + let event = utils::assert_assert_single_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); @@ -230,7 +230,7 @@ fun test_interchain_transfer_nonempty_data() { amount, &data, ); - let event = utils::assert_single_event>(); + let event = utils::assert_assert_single_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 96fd7cb9..94121e64 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -435,7 +435,7 @@ fun test_register_coin() { let coin_management = its::coin_management::new_locked(); register_coin(&mut its, coin_info, coin_management); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); sui::test_utils::destroy(its); } @@ -463,7 +463,7 @@ fun test_deploy_remote_interchain_token() { destination_chain, ); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); let mut writer = abi::new_writer(6); @@ -503,7 +503,7 @@ fun test_deploy_interchain_token() { let token_id = register_coin(&mut its, coin_info, coin_management); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); let amount = 1234; let coin = sui::coin::mint_for_testing(amount, ctx); @@ -527,7 +527,7 @@ fun test_deploy_interchain_token() { &clock, ); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); let mut writer = abi::new_writer(6); @@ -599,7 +599,7 @@ fun test_receive_interchain_transfer() { receive_interchain_transfer(&mut its, approved_message, &clock, ctx); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); @@ -661,7 +661,7 @@ fun test_receive_interchain_transfer_with_data() { ctx, ); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); @@ -710,7 +710,7 @@ fun test_receive_deploy_interchain_token() { receive_deploy_interchain_token(&mut its, approved_message); - utils::assert_single_event>(); + utils::assert_assert_single_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); diff --git a/move/utils/sources/utils/utils.move b/move/utils/sources/utils/utils.move index 206fe24f..8529e8a5 100644 --- a/move/utils/sources/utils/utils.move +++ b/move/utils/sources/utils/utils.move @@ -40,7 +40,7 @@ public fun assert_events(n: u64): vector { } #[test_only] -public fun assert_single_event(): T { +public fun assert_assert_single_event(): T { let events = assert_events(1); events[0] } From 76733ca4612655bcca83a58fcf8806893e18d426 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 20 Nov 2024 17:55:42 +0200 Subject: [PATCH 12/26] rename assert_single_event to assert_event --- move/axelar_gateway/sources/channel.move | 4 ++-- move/axelar_gateway/sources/gateway.move | 8 ++++---- move/its/sources/events.move | 4 ++-- move/its/sources/its.move | 14 +++++++------- move/utils/sources/utils/utils.move | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/move/axelar_gateway/sources/channel.move b/move/axelar_gateway/sources/channel.move index df2cdecc..45ad15bd 100644 --- a/move/axelar_gateway/sources/channel.move +++ b/move/axelar_gateway/sources/channel.move @@ -199,9 +199,9 @@ public(package) fun approved_message_payload( fun test_new_and_destroy() { let ctx = &mut sui::tx_context::dummy(); let channel: Channel = new(ctx); - utils::assert_single_event(); + utils::assert_event(); channel.destroy(); - utils::assert_single_event(); + utils::assert_event(); } #[test] diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index b97b9c1d..fe2ebcac 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -603,7 +603,7 @@ fun test_take_approved_message() { payload, ); - utils::assert_single_event(); + utils::assert_event(); let expected_approved_message = axelar_gateway::channel::create_approved_message( source_chain, @@ -666,7 +666,7 @@ fun test_approve_messages() { self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); - utils::assert_single_event(); + utils::assert_event(); clock.destroy_for_testing(); sui::test_utils::destroy(self) @@ -768,7 +768,7 @@ fun test_rotate_signers() { ctx, ); - utils::assert_single_event(); + utils::assert_event(); let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( @@ -1053,7 +1053,7 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); - utils::assert_single_event(); + utils::assert_event(); sui::test_utils::destroy(gateway); channel.destroy(); diff --git a/move/its/sources/events.move b/move/its/sources/events.move index c66a22b8..2569da19 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -202,7 +202,7 @@ fun test_interchain_transfer_empty_data() { amount, &data, ); - let event = utils::assert_single_event>(); + let event = utils::assert_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); @@ -230,7 +230,7 @@ fun test_interchain_transfer_nonempty_data() { amount, &data, ); - let event = utils::assert_single_event>(); + let event = utils::assert_event>(); assert!(event.data_hash == data_hash); assert!(event.source_address == source_address); diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 96fd7cb9..86b2481e 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -435,7 +435,7 @@ fun test_register_coin() { let coin_management = its::coin_management::new_locked(); register_coin(&mut its, coin_info, coin_management); - utils::assert_single_event>(); + utils::assert_event>(); sui::test_utils::destroy(its); } @@ -463,7 +463,7 @@ fun test_deploy_remote_interchain_token() { destination_chain, ); - utils::assert_single_event>(); + utils::assert_event>(); let mut writer = abi::new_writer(6); @@ -503,7 +503,7 @@ fun test_deploy_interchain_token() { let token_id = register_coin(&mut its, coin_info, coin_management); - utils::assert_single_event>(); + utils::assert_event>(); let amount = 1234; let coin = sui::coin::mint_for_testing(amount, ctx); @@ -527,7 +527,7 @@ fun test_deploy_interchain_token() { &clock, ); - utils::assert_single_event>(); + utils::assert_event>(); let mut writer = abi::new_writer(6); @@ -599,7 +599,7 @@ fun test_receive_interchain_transfer() { receive_interchain_transfer(&mut its, approved_message, &clock, ctx); - utils::assert_single_event>(); + utils::assert_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); @@ -661,7 +661,7 @@ fun test_receive_interchain_transfer_with_data() { ctx, ); - utils::assert_single_event>(); + utils::assert_event>(); assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); @@ -710,7 +710,7 @@ fun test_receive_deploy_interchain_token() { receive_deploy_interchain_token(&mut its, approved_message); - utils::assert_single_event>(); + utils::assert_event>(); clock.destroy_for_testing(); sui::test_utils::destroy(its); diff --git a/move/utils/sources/utils/utils.move b/move/utils/sources/utils/utils.move index 206fe24f..e5b57ac8 100644 --- a/move/utils/sources/utils/utils.move +++ b/move/utils/sources/utils/utils.move @@ -33,14 +33,14 @@ public macro fun peel<$T>($data: vector, $peel_fn: |&mut BCS| -> $T): $T { use sui::event; #[test_only] -public fun assert_events(n: u64): vector { +public fun assert_events(event_count: u64): vector { let events = event::events_by_type(); - assert!(events.length() == n); + assert!(events.length() == event_count); events } #[test_only] -public fun assert_single_event(): T { +public fun assert_event(): T { let events = assert_events(1); events[0] } From 90cabf62608e8ca2c05f710e4eef114d3448fbe5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 21 Nov 2024 18:23:21 +0200 Subject: [PATCH 13/26] added an upgraded version for gateway --- move/axelar_gateway_v1/Move.lock | 45 + move/axelar_gateway_v1/Move.toml | 14 + move/axelar_gateway_v1/sources/auth.move | 291 +++++ move/axelar_gateway_v1/sources/channel.move | 311 +++++ move/axelar_gateway_v1/sources/events.move | 101 ++ move/axelar_gateway_v1/sources/gateway.move | 1075 +++++++++++++++++ .../sources/types/bytes32.move | 80 ++ .../sources/types/message.move | 95 ++ .../sources/types/message_status.move | 26 + .../sources/types/message_ticket.move | 135 +++ .../sources/types/proof.move | 356 ++++++ .../sources/types/weighted_signer.move | 162 +++ .../sources/types/weighted_signers.move | 207 ++++ .../sources/versioned/gateway_v0.move | 560 +++++++++ 14 files changed, 3458 insertions(+) create mode 100644 move/axelar_gateway_v1/Move.lock create mode 100644 move/axelar_gateway_v1/Move.toml create mode 100644 move/axelar_gateway_v1/sources/auth.move create mode 100644 move/axelar_gateway_v1/sources/channel.move create mode 100644 move/axelar_gateway_v1/sources/events.move create mode 100644 move/axelar_gateway_v1/sources/gateway.move create mode 100644 move/axelar_gateway_v1/sources/types/bytes32.move create mode 100644 move/axelar_gateway_v1/sources/types/message.move create mode 100644 move/axelar_gateway_v1/sources/types/message_status.move create mode 100644 move/axelar_gateway_v1/sources/types/message_ticket.move create mode 100644 move/axelar_gateway_v1/sources/types/proof.move create mode 100644 move/axelar_gateway_v1/sources/types/weighted_signer.move create mode 100644 move/axelar_gateway_v1/sources/types/weighted_signers.move create mode 100644 move/axelar_gateway_v1/sources/versioned/gateway_v0.move diff --git a/move/axelar_gateway_v1/Move.lock b/move/axelar_gateway_v1/Move.lock new file mode 100644 index 00000000..722cda48 --- /dev/null +++ b/move/axelar_gateway_v1/Move.lock @@ -0,0 +1,45 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 3 +manifest_digest = "E9A0CD1400D2AFF354F91244ACA6D96CD91319A47BFBF47CDCC0347338FD41D2" +deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C" +dependencies = [ + { id = "MoveStdlib", name = "MoveStdlib" }, + { id = "Sui", name = "Sui" }, + { id = "Utils", name = "Utils" }, + { id = "VersionControl", name = "VersionControl" }, +] + +[[move.package]] +id = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "mainnet-v1.35.2", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +id = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "mainnet-v1.35.2", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { id = "MoveStdlib", name = "MoveStdlib" }, +] + +[[move.package]] +id = "Utils" +source = { local = "../utils" } + +dependencies = [ + { id = "Sui", name = "Sui" }, +] + +[[move.package]] +id = "VersionControl" +source = { local = "../version_control" } + +dependencies = [ + { id = "Sui", name = "Sui" }, +] + +[move.toolchain-version] +compiler-version = "1.35.2" +edition = "2024.beta" +flavor = "sui" diff --git a/move/axelar_gateway_v1/Move.toml b/move/axelar_gateway_v1/Move.toml new file mode 100644 index 00000000..86029da2 --- /dev/null +++ b/move/axelar_gateway_v1/Move.toml @@ -0,0 +1,14 @@ +[package] +name = "AxelarGateway" +version = "0.1.0" +edition = "2024.beta" + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet-v1.35.2" } +MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "mainnet-v1.35.2" } +VersionControl = { local = "../version_control" } +Utils = { local = "../utils" } + +[addresses] +axelar_gateway = "0xa1" +clock = "0x6" diff --git a/move/axelar_gateway_v1/sources/auth.move b/move/axelar_gateway_v1/sources/auth.move new file mode 100644 index 00000000..295c338f --- /dev/null +++ b/move/axelar_gateway_v1/sources/auth.move @@ -0,0 +1,291 @@ +module axelar_gateway::auth; + +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::events; +use axelar_gateway::proof::Proof; +use axelar_gateway::weighted_signers::WeightedSigners; +use sui::bcs; +use sui::clock::Clock; +use sui::table::{Self, Table}; + +// ------ +// Errors +// ------ +#[error] +const EInsufficientRotationDelay: vector = b"insufficient rotation delay"; + +#[error] +const EInvalidEpoch: vector = + b"the difference between current_epoch and signers_epoch exceeds the allowed retention period"; + +// ----- +// 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, +} + +// ----------------- +// 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 +} + +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, + ); + + proof.validate( + bcs::to_bytes( + &MessageToSign { + domain_separator: self.domain_separator, + signers_hash, + data_hash, + }, + ), + ); + + is_latest_signers +} + +public(package) fun rotate_signers( + self: &mut AxelarSigners, + clock: &Clock, + new_signers: WeightedSigners, + enforce_rotation_delay: bool, +) { + new_signers.validate(); + + 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; + + events::signers_rotated( + epoch, + new_signers_hash, + new_signers, + ); +} + +// ------------------ +// Internal Functions +// ------------------ +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 +// --------- +#[test_only] +use sui::ecdsa_k1; +#[test_only] +use axelar_gateway::proof; + +#[test_only] +public fun dummy(ctx: &mut TxContext): AxelarSigners { + let mut rng = sui::random::new_generator_for_testing(); + AxelarSigners { + epoch: 0, + epoch_by_signers_hash: table::new(ctx), + domain_separator: bytes32::from_bytes(rng.generate_bytes(32)), + minimum_rotation_delay: 1, + last_rotation_timestamp: 0, + previous_signers_retention: 3, + } +} + +#[test_only] +public(package) fun new_message_to_sign( + domain_separator: Bytes32, + signers_hash: Bytes32, + data_hash: Bytes32, +): MessageToSign { + MessageToSign { + domain_separator, + signers_hash, + data_hash, + } +} + +#[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(package) fun epoch_mut(self: &mut AxelarSigners): &mut u64 { + &mut self.epoch +} + +#[test_only] +public(package) fun generate_proof( + data_hash: Bytes32, + domain_separator: Bytes32, + weighted_signers: WeightedSigners, + keypairs: &vector, +): Proof { + let message_to_sign = bcs::to_bytes( + &MessageToSign { + domain_separator, + signers_hash: weighted_signers.hash(), + data_hash, + }, + ); + + proof::generate(weighted_signers, &message_to_sign, keypairs) +} + +// ----- +// Tests +// ----- +#[test] +fun test_new() { + let ctx = &mut sui::tx_context::dummy(); + let self = new(ctx); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EInsufficientRotationDelay)] +fun test_update_rotation_timestamp_insufficient_rotation_delay() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + + let clock = sui::clock::create_for_testing(ctx); + + self.last_rotation_timestamp = 1; + + self.update_rotation_timestamp(&clock, true); + + sui::test_utils::destroy(self); + clock.destroy_for_testing(); +} + +#[test] +#[expected_failure(abort_code = EInvalidEpoch)] +fun test_validate_proof_invalid_epoch() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let mut self = new(ctx); + let proof = axelar_gateway::proof::create_for_testing( + axelar_gateway::weighted_signers::create_for_testing( + vector[], + 0, + bytes32::from_bytes(rng.generate_bytes(32)), + ), + vector[], + ); + let signers = proof.signers(); + let signers_hash = signers.hash(); + let signers_epoch = 0; + self.epoch_by_signers_hash.add(signers_hash, signers_epoch); + + self.previous_signers_retention = 2; + self.epoch = 3; + + self.validate_proof( + bytes32::from_bytes(rng.generate_bytes(32)), + proof, + ); + + sui::test_utils::destroy(self); +} diff --git a/move/axelar_gateway_v1/sources/channel.move b/move/axelar_gateway_v1/sources/channel.move new file mode 100644 index 00000000..45ad15bd --- /dev/null +++ b/move/axelar_gateway_v1/sources/channel.move @@ -0,0 +1,311 @@ +/// Channels +/// +/// 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 axelar_gateway::events; +use std::ascii::String; + +// ----- +// 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, +} + +/// 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, +} + +// ------ +// Errors +// ------ + +/// If approved message is consumed by an invalid destination id +#[error] +const EInvalidDestination: vector = b"invalid destination"; + +// ---------------- +// Public Functions +// ---------------- + +/// 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); + + events::channel_created(id.uid_to_address()); + + Channel { + id, + } +} + +/// 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; + + events::channel_destroyed(id.uid_to_address()); + + id.delete(); +} + +public fun id(self: &Channel): ID { + object::id(self) +} + +public fun to_address(self: &Channel): address { + object::id_address(self) +} + +/// 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) +} + +// ----------------- +// 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 Only +// --------- +#[test_only] +use utils::utils; + +#[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_only] +public fun destroy_for_testing(approved_message: ApprovedMessage) { + ApprovedMessage { + source_chain: _, + message_id: _, + source_address: _, + destination_id: _, + payload: _, + } = approved_message; +} + +#[test_only] +public(package) fun approved_message_source_chain( + self: &ApprovedMessage, +): String { + self.source_chain +} + +#[test_only] +public(package) fun approved_message_message_id( + self: &ApprovedMessage, +): String { + self.message_id +} + +#[test_only] +public(package) fun approved_message_source_address( + self: &ApprovedMessage, +): String { + self.source_address +} + +#[test_only] +public(package) fun approved_message_destination_id( + self: &ApprovedMessage, +): address { + self.destination_id +} + +#[test_only] +public(package) fun approved_message_payload( + self: &ApprovedMessage, +): vector { + self.payload +} + +// ----- +// Tests +// ----- +#[test] +fun test_new_and_destroy() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + utils::assert_event(); + channel.destroy(); + utils::assert_event(); +} + +#[test] +fun test_id() { + let ctx = &mut sui::tx_context::dummy(); + let channel: Channel = new(ctx); + assert!(channel.id() == object::id(&channel)); + 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)); + channel.destroy() +} + +#[test] +fun test_create_approved_message() { + let mut rng = sui::random::new_generator_for_testing(); + 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 = sui::address::from_bytes(rng.generate_bytes(32)); + let input_payload = rng.generate_bytes(32); + 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); + assert!(message_id == input_message_id); + assert!(source_address == input_source_address); + assert!(destination_id == input_destination_id); + assert!(payload == input_payload); +} + +#[test] +fun test_consume_approved_message() { + let mut rng = sui::random::new_generator_for_testing(); + 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 = rng.generate_bytes(32); + 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); + assert!(message_id == input_message_id); + assert!(source_address == input_source_address); + assert!(payload == input_payload); + + channel.destroy(); +} + +#[test] +#[expected_failure(abort_code = EInvalidDestination)] +fun test_consume_approved_message_wrong_destination() { + let mut rng = sui::random::new_generator_for_testing(); + 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 = sui::address::from_bytes(rng.generate_bytes(32)); + let payload = rng.generate_bytes(32); + + 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_v1/sources/events.move b/move/axelar_gateway_v1/sources/events.move new file mode 100644 index 00000000..29247a9b --- /dev/null +++ b/move/axelar_gateway_v1/sources/events.move @@ -0,0 +1,101 @@ +module axelar_gateway::events; + +use axelar_gateway::bytes32::Bytes32; +use axelar_gateway::message::Message; +use axelar_gateway::weighted_signers::WeightedSigners; +use std::ascii::String; +use sui::event; + +// ------ +// Events +// ------ +/// Emitted when signers are rotated. +public struct SignersRotated has copy, drop { + epoch: u64, + signers_hash: Bytes32, + signers: WeightedSigners, +} + +/// Emitted when a new channel is created. +public struct ChannelCreated has copy, drop { + id: address, +} + +/// Emitted when a channel is destroyed. +public struct ChannelDestroyed has copy, drop { + id: address, +} + +/// 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, +} + +/// Emitted when a message is taken to be executed by a channel. +public struct MessageExecuted has copy, drop { + message: Message, +} + +// ----------------- +// Package Functions +// ----------------- +public(package) fun signers_rotated( + epoch: u64, + signers_hash: Bytes32, + signers: WeightedSigners, +) { + event::emit(SignersRotated { + epoch, + signers_hash, + signers, + }); +} + +public(package) fun channel_created(id: address) { + event::emit(ChannelCreated { + id, + }); +} + +public(package) fun channel_destroyed(id: address) { + event::emit(ChannelDestroyed { + id, + }); +} + +public(package) fun contract_call( + source_id: address, + destination_chain: String, + destination_address: String, + payload: vector, + payload_hash: address, +) { + event::emit(ContractCall { + source_id, + destination_chain, + destination_address, + payload, + payload_hash, + }); +} + +public(package) fun message_approved(message: Message) { + event::emit(MessageApproved { + message, + }); +} + +public(package) fun message_executed(message: Message) { + event::emit(MessageExecuted { + message, + }); +} diff --git a/move/axelar_gateway_v1/sources/gateway.move b/move/axelar_gateway_v1/sources/gateway.move new file mode 100644 index 00000000..6f0d7b84 --- /dev/null +++ b/move/axelar_gateway_v1/sources/gateway.move @@ -0,0 +1,1075 @@ +/// Implementation of the Axelar Gateway for Sui Move. +/// +/// This code is based on the following: +/// +/// - When call approvals is sent to Sui, it targets an object and not a module; +/// - To support cross-chain messaging, a Channel object has to be created; +/// - Channel can be either owned or shared but not frozen; +/// - Module developer on the Sui side will have to implement a system to +/// support messaging; +/// - Checks for uniqueness of approvals should be done through `Channel`s to +/// avoid big value storage; +/// +/// I. Sending call approvals +/// +/// A approval is sent through the `send` function, a Channel is supplied to +/// determine the source -> ID. +/// Event is then emitted and Axelar network can operate +/// +/// II. Receiving call approvals +/// +/// Approval bytes and signatures are passed into `create` function to generate +/// a CallApproval object. +/// - Signatures are checked against the known set of signers. +/// - CallApproval bytes are parsed to determine: source, destination_chain, +/// payload and destination_id +/// - `destination_id` points to a `Channel` object +/// +/// Once created, `CallApproval` needs to be consumed. And the only way to do it +/// is by calling +/// `consume_call_approval` function and pass a correct `Channel` instance +/// alongside the `CallApproval`. +/// - CallApproval is checked for uniqueness (for this channel) +/// - CallApproval is checked to match the `Channel`.id +/// +/// The Gateway object uses a versioned field to support upgradability. The +/// current implementation uses Gateway_v0. +module axelar_gateway::gateway; + +use axelar_gateway::auth::{Self, validate_proof}; +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::channel::{Channel, ApprovedMessage}; +use axelar_gateway::gateway_v0::{Self, Gateway_v0}; +use axelar_gateway::message_ticket::{Self, MessageTicket}; +use axelar_gateway::weighted_signers; +use std::ascii::{Self, String}; +use sui::clock::Clock; +use sui::table; +use sui::versioned::{Self, Versioned}; +use utils::utils; +use version_control::version_control::{Self, VersionControl}; + +// ------- +// Version +// ------- +const VERSION: u64 = 1; + +// ------- +// Structs +// ------- +public struct Gateway has key { + id: UID, + inner: Versioned, +} + +// ------------ +// Capabilities +// ------------ +public struct CreatorCap has key, store { + id: UID, +} + +// ----- +// Setup +// ----- + +/// Init the module by giving a CreatorCap to the sender to allow a full +/// `setup`. +fun init(ctx: &mut TxContext) { + let cap = CreatorCap { + id: object::new(ctx), + }; + + transfer::transfer(cap, ctx.sender()); +} + +/// Setup the module by creating a new Gateway object. +entry fun setup( + cap: CreatorCap, + operator: address, + domain_separator: address, + minimum_rotation_delay: u64, + previous_signers_retention: u64, + initial_signers: vector, + clock: &Clock, + ctx: &mut TxContext, +) { + let CreatorCap { id } = cap; + id.delete(); + + let inner = versioned::create( + VERSION, + gateway_v0::new( + operator, + table::new(ctx), + auth::setup( + bytes32::new(domain_separator), + minimum_rotation_delay, + previous_signers_retention, + utils::peel!( + initial_signers, + |bcs| weighted_signers::peel(bcs), + ), + clock, + ctx, + ), + version_control(), + ), + ctx, + ); + + // Share the gateway object for anyone to use. + transfer::share_object(Gateway { + id: object::new(ctx), + inner, + }); +} + +// ------ +// Macros +// ------ +/// This macro also uses version control to sinplify things a bit. +macro fun value($self: &Gateway, $function_name: vector): &Gateway_v0 { + let gateway = $self; + let value = gateway.inner.load_value(); + value.version_control().check(VERSION, ascii::string($function_name)); + value +} + +/// This macro also uses version control to sinplify things a bit. +macro fun value_mut( + $self: &mut Gateway, + $function_name: vector, +): &mut Gateway_v0 { + let gateway = $self; + let value = gateway.inner.load_value_mut(); + value.version_control().check(VERSION, ascii::string($function_name)); + value +} + +// ----------- +// 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 value = self.value_mut!(b"approve_messages"); + value.approve_messages(message_data, proof_data); +} + +/// 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 value = self.value_mut!(b"rotate_signers"); + value.rotate_signers( + clock, + new_signers_data, + proof_data, + ctx, + ) +} + +/// This function should only be called once +/// (checks should be made on versioned to ensure this) +/// It upgrades the version control to the new version control. +entry fun migrate(self: &mut Gateway) { + self.inner.load_value_mut().migrate(version_control()); +} + +// ---------------- +// Public Functions +// ---------------- + +/// Prepare a MessageTicket to call a contract on the destination chain. +public fun prepare_message( + channel: &Channel, + destination_chain: String, + destination_address: String, + payload: vector, +): MessageTicket { + message_ticket::new( + channel.to_address(), + destination_chain, + destination_address, + payload, + VERSION, + ) +} + +/// Submit the MessageTicket which causes a contract call by sending an event +/// from an +/// authorized Channel. +public fun send_message(self: &Gateway, message: MessageTicket) { + let value = self.value!(b"send_message"); + value.send_message(message, VERSION); +} + +public fun is_message_approved( + self: &Gateway, + source_chain: String, + message_id: String, + source_address: String, + destination_id: address, + payload_hash: Bytes32, +): bool { + let value = self.value!(b"is_message_approved"); + value.is_message_approved( + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + ) +} + +public fun is_message_executed( + self: &Gateway, + source_chain: String, + message_id: String, +): bool { + let value = self.value!(b"is_message_executed"); + value.is_message_executed(source_chain, message_id) +} + +/// 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 value = self.value_mut!(b"take_approved_message"); + value.take_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ) +} + +// ----------------- +// Private Functions +// ----------------- + +fun version_control(): VersionControl { + version_control::new(vector[ + vector[ + b"approve_messages", + b"rotate_signers", + b"is_message_approved", + b"is_message_executed", + b"take_approved_message", + ].map!(|function_name| function_name.to_ascii_string()), + vector[ + b"approve_messages", + b"rotate_signers", + b"is_message_approved", + b"is_message_executed", + b"take_approved_message", + b"send_message", + ].map!(|function_name| function_name.to_ascii_string()), + ]) +} + +// --------- +// Test Only +// --------- +#[test_only] +use sui::bcs; +#[test_only] +use axelar_gateway::auth::generate_proof; +#[test_only] +use axelar_gateway::events; + +#[test_only] +public fun create_for_testing( + operator: address, + domain_separator: Bytes32, + minimum_rotation_delay: u64, + previous_signers_retention: u64, + initial_signers: weighted_signers::WeightedSigners, + clock: &Clock, + ctx: &mut TxContext, +): Gateway { + let inner = versioned::create( + VERSION, + gateway_v0::new( + operator, + table::new(ctx), + auth::setup( + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + clock, + ctx, + ), + version_control(), + ), + ctx, + ); + Gateway { + id: object::new(ctx), + inner, + } +} + +#[test_only] +fun dummy(ctx: &mut TxContext): Gateway { + let mut rng = sui::random::new_generator_for_testing(); + let inner = versioned::create( + VERSION, + gateway_v0::new( + sui::address::from_bytes(rng.generate_bytes(32)), + table::new(ctx), + auth::dummy(ctx), + version_control::new(vector[ + vector[], + vector[ + b"approve_messages", + b"rotate_signers", + b"is_message_approved", + b"is_message_executed", + b"take_approved_message", + b"send_message", + b"", + ].map!(|function_name| function_name.to_ascii_string()), + ]), + ), + ctx, + ); + Gateway { + id: object::new(ctx), + inner, + } +} + +#[test_only] +public fun destroy_for_testing(self: Gateway) { + let Gateway { + id, + inner, + } = self; + id.delete(); + + let value = inner.destroy(); + let (_, messages, signers, _) = value.destroy_for_testing(); + + let (_, table, _, _, _, _) = signers.destroy_for_testing(); + table.destroy_empty(); + messages.destroy_empty(); +} + +// ---- +// Test +// ---- +#[test] +fun test_init() { + let mut ts = sui::test_scenario::begin(@0x0); + + init(ts.ctx()); + ts.next_tx(@0x0); + + let creator_cap = ts.take_from_sender(); + ts.return_to_sender(creator_cap); + ts.end(); +} + +#[test] +fun test_setup() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = sui::address::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = axelar_gateway::weighted_signers::dummy(); + let mut clock = sui::clock::create_for_testing(ctx); + let timestamp = rng.generate_u64(); + 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); + + let gateway_id = shared[0]; + let gateway = scenario.take_shared_by_id(gateway_id); + let Gateway { + id, + inner, + } = gateway; + id.delete(); + + let (operator_result, messages, signers, _) = inner + .destroy() + .destroy_for_testing(); + + assert!(operator == operator_result); + 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); + assert!(signer_epoch == 1); + assert!(bytes32::new(domain_separator) == domain_separator_result); + assert!(minimum_rotation_delay == minimum_rotation_delay_result); + assert!(last_rotation_timestamp == timestamp); + assert!(previous_signers_retention == previous_signers_retention_result); + + clock.destroy_for_testing(); + scenario.end(); +} + +#[test] +#[expected_failure] +fun test_setup_remaining_bytes() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = sui::address::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = axelar_gateway::weighted_signers::dummy(); + let mut clock = sui::clock::create_for_testing(ctx); + let timestamp = rng.generate_u64(); + clock.increment_for_testing(timestamp); + + let creator_cap = CreatorCap { + id: object::new(ctx), + }; + + let mut scenario = sui::test_scenario::begin(@0x1); + let mut initial_signers_bytes = bcs::to_bytes(&initial_signers); + initial_signers_bytes.push_back(0); + setup( + creator_cap, + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers_bytes, + &clock, + scenario.ctx(), + ); + + let tx_effects = scenario.next_tx(@0x1); + let shared = tx_effects.shared(); + + assert!(shared.length() == 1); + + let gateway_id = shared[0]; + let gateway = scenario.take_shared_by_id(gateway_id); + let Gateway { + id, + inner, + } = gateway; + id.delete(); + + let (operator_result, messages, signers, _) = inner + .destroy() + .destroy_for_testing(); + + assert!(operator == operator_result); + 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); + assert!(signer_epoch == 1); + assert!(bytes32::new(domain_separator) == domain_separator_result); + assert!(minimum_rotation_delay == minimum_rotation_delay_result); + assert!(last_rotation_timestamp == timestamp); + assert!(previous_signers_retention == previous_signers_retention_result); + + clock.destroy_for_testing(); + scenario.end(); +} + +#[test] +fun test_peel_weighted_signers() { + let signers = axelar_gateway::weighted_signers::dummy(); + let bytes = bcs::to_bytes(&signers); + let result = utils::peel!(bytes, |bcs| weighted_signers::peel(bcs)); + + assert!(result == signers); +} + +#[test] +#[expected_failure] +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); + + utils::peel!(bytes, |bcs| weighted_signers::peel(bcs)); +} + +#[test] +fun test_peel_proof() { + let proof = axelar_gateway::proof::dummy(); + let bytes = bcs::to_bytes(&proof); + let result = utils::peel!(bytes, |bcs| axelar_gateway::proof::peel(bcs)); + + assert!(result == proof); +} + +#[test] +#[expected_failure] +fun test_peel_proof_no_remaining_data() { + let proof = axelar_gateway::proof::dummy(); + let mut bytes = bcs::to_bytes(&proof); + bytes.push_back(0); + + utils::peel!(bytes, |bcs| axelar_gateway::proof::peel(bcs)); +} + +#[test] +fun test_take_approved_message() { + let mut rng = sui::random::new_generator_for_testing(); + let mut gateway = dummy(&mut sui::tx_context::dummy()); + 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 = sui::address::from_bytes(rng.generate_bytes(32)); + let payload = rng.generate_bytes(32); + let payload_hash = axelar_gateway::bytes32::new( + sui::address::from_bytes(sui::hash::keccak256(&payload)), + ); + let message = axelar_gateway::message::new( + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + ); + + gateway + .value_mut!(b"") + .messages_mut() + .add( + message.command_id(), + axelar_gateway::message_status::approved(message.hash()), + ); + + let approved_message = gateway.take_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ); + + utils::assert_event(); + + let expected_approved_message = axelar_gateway::channel::create_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ); + assert!(&approved_message == &expected_approved_message); + + gateway.value_mut!(b"").messages_mut().remove(message.command_id()); + + approved_message.destroy_for_testing(); + expected_approved_message.destroy_for_testing(); + gateway.destroy_for_testing(); +} + +#[test] +fun test_approve_messages() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + let messages = vector[ + axelar_gateway::message::dummy(), + ]; + let data_hash = gateway_v0::approve_messages_data_hash(messages); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + + self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); + + utils::assert_event(); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self) +} + +#[test] +#[expected_failure] +fun test_approve_messages_remaining_data() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + let messages = vector[axelar_gateway::message::dummy()]; + let data_hash = gateway_v0::approve_messages_data_hash(messages); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + let mut proof_data = bcs::to_bytes(&proof); + proof_data.push_back(0); + + self.approve_messages(bcs::to_bytes(&messages), proof_data); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self) +} + +#[test] +fun test_rotate_signers() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let next_weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ).public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let mut clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + + utils::assert_event(); + + let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + + clock.increment_for_testing(minimum_rotation_delay); + self.rotate_signers( + &clock, + bcs::to_bytes(&next_weighted_signers), + bcs::to_bytes(&proof), + ctx, + ); + + utils::assert_events(2); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure] +fun test_rotate_signers_remaining_data_message_data() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let next_weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ).public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let mut clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + + let mut message_data = bcs::to_bytes(&next_weighted_signers); + message_data.push_back(0); + + let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + + clock.increment_for_testing(minimum_rotation_delay); + self.rotate_signers(&clock, message_data, bcs::to_bytes(&proof), ctx); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure] +fun test_rotate_signers_remaining_data_proof_data() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let next_weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ).public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let mut clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + + let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + let mut proof_data = bcs::to_bytes(&proof); + proof_data.push_back(0); + + clock.increment_for_testing(minimum_rotation_delay); + self.rotate_signers( + &clock, + bcs::to_bytes(&next_weighted_signers), + proof_data, + ctx, + ); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = axelar_gateway::gateway_v0::ENotLatestSigners)] +fun test_rotate_signers_not_latest_signers() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ); + let weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *keypair.public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + let next_weighted_signers = weighted_signers::create_for_testing( + vector[ + axelar_gateway::weighted_signer::new( + *sui::ecdsa_k1::secp256k1_keypair_from_seed( + &rng.generate_bytes(32), + ).public_key(), + 1, + ), + ], + 1, + bytes32::from_bytes(rng.generate_bytes(32)), + ); + + let operator = sui::address::from_bytes(rng.generate_bytes(32)); + let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); + let minimum_rotation_delay = rng.generate_u64(); + let previous_signers_retention = rng.generate_u64(); + let initial_signers = weighted_signers; + let mut clock = sui::clock::create_for_testing(ctx); + let mut self = create_for_testing( + operator, + domain_separator, + minimum_rotation_delay, + previous_signers_retention, + initial_signers, + &clock, + ctx, + ); + // Tell the gateway this is not the latest epoch + let epoch = self.value_mut!(b"rotate_signers").signers_mut().epoch_mut(); + *epoch = *epoch + 1; + + let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let proof = generate_proof( + data_hash, + domain_separator, + weighted_signers, + &vector[keypair], + ); + + clock.increment_for_testing(minimum_rotation_delay); + self.rotate_signers( + &clock, + bcs::to_bytes(&next_weighted_signers), + bcs::to_bytes(&proof), + ctx, + ); + + clock.destroy_for_testing(); + sui::test_utils::destroy(self); +} + +#[test] +fun test_is_message_approved() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + + let source_chain = ascii::string(b"Source Chain"); + let source_address = ascii::string(b"Source Address"); + let message_id = ascii::string(b"Message Id"); + let destination_id = sui::address::from_bytes(rng.generate_bytes(32)); + let payload_hash = bytes32::from_bytes(rng.generate_bytes(32)); + let message = axelar_gateway::message::new( + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + ); + + let mut gateway = dummy(ctx); + gateway.value_mut!(b"").approve_message_for_testing(message); + assert!( + gateway.is_message_approved( + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + ), + ); + assert!( + !gateway.is_message_executed( + source_chain, + message_id, + ), + ); + + sui::test_utils::destroy(gateway); +} + +#[test] +fun test_send_message() { + let mut rng = sui::random::new_generator_for_testing(); + let ctx = &mut sui::tx_context::dummy(); + let channel = axelar_gateway::channel::new(ctx); + let destination_chain = ascii::string(b"Destination Chain"); + let destination_address = ascii::string(b"Destination Address"); + let payload = rng.generate_bytes(32); + let message_ticket = prepare_message( + &channel, + destination_chain, + destination_address, + payload, + ); + + let gateway = dummy(ctx); + gateway.send_message(message_ticket); + + utils::assert_event(); + + sui::test_utils::destroy(gateway); + channel.destroy(); +} diff --git a/move/axelar_gateway_v1/sources/types/bytes32.move b/move/axelar_gateway_v1/sources/types/bytes32.move new file mode 100644 index 00000000..704175f0 --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/bytes32.move @@ -0,0 +1,80 @@ +module axelar_gateway::bytes32; + +use sui::address; +use sui::bcs::BCS; + +// ----- +// Types +// ----- + +public struct Bytes32 has copy, drop, store { + bytes: address, +} + +// --------- +// Constants +// --------- + +const LENGTH: u64 = 32; + +// ---------------- +// Public Functions +// ---------------- + +/// Casts an address to a bytes32 +public fun new(bytes: address): Bytes32 { + Bytes32 { bytes: bytes } +} + +public fun default(): Bytes32 { + Bytes32 { bytes: @0x0 } +} + +public fun from_bytes(bytes: vector): Bytes32 { + new(address::from_bytes(bytes)) +} + +public fun from_address(addr: address): Bytes32 { + new(addr) +} + +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()) +} + +// ----- +// Tests +// ----- + +#[test] +public fun test_new() { + let addr = address::from_u256(sui::random::new_generator_for_testing().generate_u256()); + let actual = new(addr); + + assert!(actual.to_bytes() == addr.to_bytes()); + assert!(actual.length() == LENGTH); +} + +#[test] +public fun test_default() { + let default = default(); + + assert!(default.bytes == @0x0); + assert!(default.length() == LENGTH); +} + +#[test] +public fun test_from_address() { + let addr = address::from_u256(sui::random::new_generator_for_testing().generate_u256()); + let bytes32 = from_address(addr); + assert!(bytes32.bytes == addr); + assert!(bytes32.length() == LENGTH); +} diff --git a/move/axelar_gateway_v1/sources/types/message.move b/move/axelar_gateway_v1/sources/types/message.move new file mode 100644 index 00000000..bdd361a0 --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/message.move @@ -0,0 +1,95 @@ +module axelar_gateway::message; + +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, +} + +/// ----------------- +/// 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); + + 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()); + + 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 hash(self: &Message): Bytes32 { + bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) +} + +// --------- +// Test Only +// --------- +#[test_only] +public(package) fun dummy(): Message { + let source_chain = std::ascii::string(b"Source Chain"); + let source_address = std::ascii::string(b"Source Address"); + let message_id = std::ascii::string(b"Message Id"); + let destination_id = @0x4; + let payload_hash = bytes32::new(@0x5); + Message { + source_chain, + message_id, + source_address, + destination_id, + payload_hash, + } +} diff --git a/move/axelar_gateway_v1/sources/types/message_status.move b/move/axelar_gateway_v1/sources/types/message_status.move new file mode 100644 index 00000000..796153f7 --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/message_status.move @@ -0,0 +1,26 @@ +module axelar_gateway::message_status; + +use axelar_gateway::bytes32::Bytes32; + +// ----- +// Types +// ----- +/// The Status of the message. +/// Can be either one of two statuses: +/// - Approved: Set to the hash of the message +/// - Executed: Message was already executed +public enum MessageStatus has copy, drop, store { + Approved(Bytes32), + Executed, +} + +// ----------------- +// Package Functions +// ----------------- +public(package) fun approved(hash: Bytes32): MessageStatus { + MessageStatus::Approved(hash) +} + +public(package) fun executed(): MessageStatus { + MessageStatus::Executed +} diff --git a/move/axelar_gateway_v1/sources/types/message_ticket.move b/move/axelar_gateway_v1/sources/types/message_ticket.move new file mode 100644 index 00000000..176bf43f --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/message_ticket.move @@ -0,0 +1,135 @@ +module axelar_gateway::message_ticket; + +use std::ascii::String; + +// ----- +// Types +// ----- +/// This hot potato object is created to capture all the information about a +/// remote contract call. +/// In can then be submitted to the gateway to send the Message. +/// It is advised that modules return this Message ticket to be submitted by the +/// frontend, so that when the gateway package is upgraded, the app doesn't need +/// to upgrade as well, ensuring forward compatibility. +/// The version is captured to ensure that future packages can restrict which +/// messages they can send, and to ensure that no future messages are sent from +/// earlier versions. +public struct MessageTicket { + source_id: address, + destination_chain: String, + destination_address: String, + payload: vector, + version: u64, +} + +// ------- +// Getters +// ------- +public fun source_id(self: &MessageTicket): address { + self.source_id +} + +public fun destination_chain(self: &MessageTicket): String { + self.destination_chain +} + +public fun destination_address(self: &MessageTicket): String { + self.destination_address +} + +public fun payload(self: &MessageTicket): vector { + self.payload +} + +public fun version(self: &MessageTicket): u64 { + self.version +} + +// ----------------- +// Package Functions +// ----------------- +public(package) fun new( + source_id: address, + destination_chain: String, + destination_address: String, + payload: vector, + version: u64, +): MessageTicket { + MessageTicket { + source_id, + destination_chain, + destination_address, + payload, + version, + } +} + +public(package) fun destroy( + self: MessageTicket, +): (address, String, String, vector, u64) { + let MessageTicket { + source_id, + destination_chain, + destination_address, + payload, + version, + } = self; + (source_id, destination_chain, destination_address, payload, version) +} + +#[test_only] +public fun new_for_testing( + source_id: address, + destination_chain: String, + destination_address: String, + payload: vector, + version: u64, +): MessageTicket { + MessageTicket { + source_id, + destination_chain, + destination_address, + payload, + version, + } +} + +#[test] +fun test_all() { + let mut rng = sui::random::new_generator_for_testing(); + let source_id = sui::address::from_u256(rng.generate_u256()); + let destination_chain = std::ascii::string(b"Destination Chain"); + let destination_address = std::ascii::string( + b"Destination Address", + ); + let payload: vector = rng.generate_bytes(256); + let version: u64 = 2; + + let message_ticket = new( + source_id, + destination_chain, + destination_address, + payload, + version, + ); + + assert!(message_ticket.source_id() == source_id); + assert!(message_ticket.destination_chain() == destination_chain); + assert!(message_ticket.destination_address() == destination_address); + assert!(message_ticket.payload() == payload); + assert!(message_ticket.version() == version); + + let ( + result_source_id, + result_destination_chain, + result_destination_address, + result_payload, + result_version, + ) = message_ticket.destroy(); + + assert!(result_source_id == source_id); + assert!(result_destination_chain == destination_chain); + assert!(result_destination_address == destination_address); + assert!(result_payload == payload); + assert!(result_version == version); +} diff --git a/move/axelar_gateway_v1/sources/types/proof.move b/move/axelar_gateway_v1/sources/types/proof.move new file mode 100644 index 00000000..ac4625de --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/proof.move @@ -0,0 +1,356 @@ +module axelar_gateway::proof; + +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, +} + +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 +#[error] +const EInvalidSignatureLength: vector = + b"invalid signature length: expected 65 bytes"; + +#[error] +const ELowSignaturesWeight: vector = b"insufficient signatures weight"; + +#[error] +const ESignerNotFound: vector = + b"no signer found with the specified public key in the given range"; + +#[error] +const ERedundantSignaturesProvided: vector = + b"redundant signatures provided"; + +// ---------------- +// 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 +} + +// ----------------- +// Package Functions +// ----------------- +public(package) fun new_signature(bytes: vector): Signature { + assert!(bytes.length() == SIGNATURE_LENGTH, EInvalidSignatureLength); + + 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) +} + +/// Validates the signatures of a message against the signers. +/// The total weight of the signatures must be greater than or equal to the +/// threshold. +/// Otherwise, the error `ELowSignaturesWeight` is raised. +public(package) fun validate(self: &Proof, message: vector) { + let signers = &self.signers; + let signatures = &self.signatures; + assert!(signatures.length() != 0, ELowSignaturesWeight); + + let threshold = signers.threshold(); + let signatures_length = signatures.length(); + let mut total_weight: u128 = 0; + let mut signer_index = 0; + let mut i = 0; + + while (i < signatures_length) { + let pub_key = signatures[i].recover_pub_key(&message); + + let (weight, index) = find_weight_by_pub_key_from( + signers, + signer_index, + &pub_key, + ); + + total_weight = total_weight + weight; + + if (total_weight >= threshold) { + if (i + 1 == signatures_length) { + return + }; + + abort ERedundantSignaturesProvided + }; + + i = i + 1; + signer_index = index + 1; + }; + + abort ELowSignaturesWeight +} + +/// Finds the weight of a signer in the weighted signers by its public key. +fun find_weight_by_pub_key_from( + signers: &WeightedSigners, + signer_index: u64, + pub_key: &vector, +): (u128, u64) { + let signers = signers.signers(); + let length = signers.length(); + let mut index = signer_index; + + // Find the first signer that satisfies the predicate + while (index < length && signers[index].pub_key() != pub_key) { + index = index + 1; + }; + + // If no signer satisfies the predicate, return an error + assert!(index < length, ESignerNotFound); + + (signers[index].weight(), index) +} + +public(package) fun peel_signature(bcs: &mut BCS): Signature { + let bytes = bcs.peel_vec_u8(); + + new_signature(bytes) +} + +public(package) fun peel(bcs: &mut BCS): Proof { + let signers = weighted_signers::peel(bcs); + let length = bcs.peel_vec_length(); + + Proof { + signers, + signatures: vector::tabulate!(length, |_| peel_signature(bcs)), + } +} + +// --------- +// Test Only +// --------- +#[test_only] +public(package) fun create_for_testing( + signers: WeightedSigners, + signatures: vector, +): Proof { + Proof { + signers, + signatures, + } +} + +#[test_only] +public(package) fun dummy(): Proof { + let mut rng = sui::random::new_generator_for_testing(); + let signature = rng.generate_bytes(SIGNATURE_LENGTH as u16); + Proof { + signers: axelar_gateway::weighted_signers::dummy(), + signatures: vector[Signature { bytes: signature }], + } +} + +#[test_only] +public(package) fun generate( + weighted_signers: WeightedSigners, + message_to_sign: &vector, + keypairs: &vector, +): Proof { + let signatures = keypairs.map_ref!( + |keypair| new_signature( + ecdsa::secp256k1_sign( + keypair.private_key(), + message_to_sign, + 0, + true, + ), + ), + ); + create_for_testing(weighted_signers, signatures) +} + +// ----- +// Tests +// ----- +#[test] +fun test_getters() { + let proof = dummy(); + + assert!(proof.signers() == proof.signers); + assert!(proof.signatures() == proof.signatures); +} + +#[test] +#[expected_failure(abort_code = EInvalidSignatureLength)] +fun test_new_signature_invalid_signature_length() { + new_signature(vector[]); +} + +#[test] +fun test_recover_pub_key() { + let mut rng = sui::random::new_generator_for_testing(); + let keypair = ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)); + let message = rng.generate_bytes(32); + let signature = new_signature( + ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), + ); + assert!(signature.recover_pub_key(&message) == keypair.public_key()); +} + +#[test] +fun test_validate() { + let mut rng = sui::random::new_generator_for_testing(); + let mut keypairs = vector[0, 1, 2].map!( + |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), + ); + let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); + let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); + let message = rng.generate_bytes(32); + let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); + + let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( + vector[0, 1, 2].map!( + |index| axelar_gateway::weighted_signer::new( + pub_keys[index], + weights[index], + ), + ), + weights[0] + weights[2], + nonce, + ); + keypairs.remove(1); + let proof = generate(weighted_signers, &message, &keypairs); + proof.validate(message); +} + +#[test] +#[expected_failure(abort_code = ERedundantSignaturesProvided)] +fun test_validate_redundant_signatures() { + let mut rng = sui::random::new_generator_for_testing(); + let keypairs = vector[0, 1, 2].map!( + |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), + ); + let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); + let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); + let message = rng.generate_bytes(32); + let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); + + let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( + vector[0, 1, 2].map!( + |index| axelar_gateway::weighted_signer::new( + pub_keys[index], + weights[index], + ), + ), + weights[0] + weights[1], + nonce, + ); + let proof = generate(weighted_signers, &message, &keypairs); + proof.validate(message); +} + +#[test] +#[expected_failure(abort_code = ELowSignaturesWeight)] +fun test_validate_empty_signers() { + let mut rng = sui::random::new_generator_for_testing(); + let message = rng.generate_bytes(32); + let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); + let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( + vector[], + 1, + nonce, + ); + let proof = create_for_testing(weighted_signers, vector[]); + proof.validate(message); +} + +#[test] +#[expected_failure(abort_code = ELowSignaturesWeight)] +fun test_validate_low_signature_weight() { + let mut rng = sui::random::new_generator_for_testing(); + let keypairs = vector[0, 1, 2].map!( + |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), + ); + let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); + let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); + let message = rng.generate_bytes(32); + let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); + + let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( + vector[0, 1, 2].map!( + |index| axelar_gateway::weighted_signer::new( + pub_keys[index], + weights[index], + ), + ), + weights[0] + weights[2] + 1, + nonce, + ); + let signatures = keypairs.map!( + |keypair| new_signature( + ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), + ), + ); + let proof = create_for_testing( + weighted_signers, + vector[signatures[0], signatures[2]], + ); + proof.validate(message); +} + +#[test] +#[expected_failure(abort_code = ESignerNotFound)] +fun test_validate_signer_not_found() { + let mut rng = sui::random::new_generator_for_testing(); + let keypairs = vector[0, 1, 2].map!( + |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), + ); + let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); + let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); + let message = rng.generate_bytes(32); + let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); + + let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( + vector[0, 2].map!( + |index| axelar_gateway::weighted_signer::new( + pub_keys[index], + weights[index], + ), + ), + weights[0] + weights[2], + nonce, + ); + let signatures = keypairs.map!( + |keypair| new_signature( + ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), + ), + ); + let proof = create_for_testing(weighted_signers, vector[signatures[1]]); + proof.validate(message); +} diff --git a/move/axelar_gateway_v1/sources/types/weighted_signer.move b/move/axelar_gateway_v1/sources/types/weighted_signer.move new file mode 100644 index 00000000..90d4479e --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/weighted_signer.move @@ -0,0 +1,162 @@ +module axelar_gateway::weighted_signer; + +use sui::bcs::BCS; + +// --------- +// Constants +// --------- + +/// Length of a public key +const PUB_KEY_LENGTH: u64 = 33; + +// ----- +// Types +// ----- + +public struct WeightedSigner has copy, drop, store { + pub_key: vector, + weight: u128, +} + +public fun pub_key(self: &WeightedSigner): vector { + self.pub_key +} + +public fun weight(self: &WeightedSigner): u128 { + self.weight +} + +// ------ +// Errors +// ------ + +#[error] +const EInvalidPubKeyLength: vector = + b"invalid public key length: expected 33 bytes"; + +#[error] +const EInvalidWeight: vector = b"invalid weight: expected non-zero value"; + +// ----------------- +// Package Functions +// ----------------- + +public(package) fun new(pub_key: vector, weight: u128): WeightedSigner { + assert!(pub_key.length() == PUB_KEY_LENGTH, EInvalidPubKeyLength); + + WeightedSigner { pub_key, weight } +} + +/// Empty weighted signer +public(package) fun default(): WeightedSigner { + let mut pub_key = @0x0.to_bytes(); + pub_key.push_back(0); + + 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(); + + new(pub_key, weight) +} + +public(package) fun validate(self: &WeightedSigner) { + assert!(self.weight != 0, EInvalidWeight); +} + +/// Check if self.signer is less than other.signer as bytes +public(package) fun lt(self: &WeightedSigner, other: &WeightedSigner): bool { + let mut i = 0; + + 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 + }; + + i = i + 1; + }; + + false +} + +// ----- +// Tests +// ----- + +#[test] +#[expected_failure(abort_code = EInvalidPubKeyLength)] +fun test_new_incorrect_pubkey() { + let mut rng = sui::random::new_generator_for_testing(); + new(vector[], rng.generate_u128()); +} + +#[test] +#[expected_failure(abort_code = EInvalidWeight)] +fun test_validate_invalid_weight() { + validate( + &WeightedSigner { + pub_key: vector[], + weight: 0, + }, + ) +} + +#[test] +fun test_pub_key() { + let mut rng = sui::random::new_generator_for_testing(); + let pub_key = rng.generate_bytes(3); + assert!( + &WeightedSigner { + pub_key, + weight: 0, + }.pub_key() == &pub_key, + ); +} + +#[test] +fun verify_default_signer() { + let signer = default(); + + assert!(signer.weight == 0); + assert!(signer.pub_key.length() == PUB_KEY_LENGTH); + + let mut i = 0; + while (i < PUB_KEY_LENGTH) { + assert!(signer.pub_key[i] == 0); + i = i + 1; + } +} + +#[test] +fun compare_weight_signers() { + let signer1 = new( + x"000100000000000000000000000000000000000000000000000000000000000000", + 1, + ); + let signer2 = new( + x"000200000000000000000000000000000000000000000000000000000000000000", + 2, + ); + let signer3 = new( + x"000100000000000000000000000000000000000000000000000000000000000001", + 3, + ); + + // Less than + assert!(signer1.lt(&signer2)); + assert!(signer1.lt(&signer3)); + + // Not less than + assert!(!signer2.lt(&signer1)); + assert!(!signer3.lt(&signer1)); + + // Equal + assert!(!signer1.lt(&signer1)); // !(signer1 < signer1) +} diff --git a/move/axelar_gateway_v1/sources/types/weighted_signers.move b/move/axelar_gateway_v1/sources/types/weighted_signers.move new file mode 100644 index 00000000..51f6153a --- /dev/null +++ b/move/axelar_gateway_v1/sources/types/weighted_signers.move @@ -0,0 +1,207 @@ +module axelar_gateway::weighted_signers; + +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, +} + +/// ------ +/// Errors +/// ------ +#[error] +const EInvalidSignersLength: vector = + b"invalid signers length: expected at least 1 signer"; + +#[error] +const EInvalidThreshold: vector = + b"invalid threshold: expected non-zero value and less than or equal to the total weight of the signers"; + +#[error] +const EInvalidSignerOrder: vector = + b"invalid signer order: signers must be in ascending order by their public key"; + +/// ----------------- +/// Package Functions +/// ----------------- + +/// Decode a `WeightedSigners` from the BCS encoded bytes. +public(package) fun peel(bcs: &mut BCS): WeightedSigners { + let len = bcs.peel_vec_length(); + assert!(len > 0, EInvalidSignersLength); + + WeightedSigners { + signers: vector::tabulate!(len, |_| weighted_signer::peel(bcs)), + threshold: bcs.peel_u128(), + nonce: bytes32::peel(bcs), + } +} + +/// Validates the weighted signers. The following must be true: +/// 1. The signers are in ascending order by their public key. +/// 2. The threshold is greater than zero. +/// 3. The threshold is less than or equal to the total weight of the signers. +public(package) fun validate(self: &WeightedSigners) { + self.validate_signers(); + self.validate_threshold(); +} + +public(package) fun hash(self: &WeightedSigners): Bytes32 { + bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) +} + +public(package) fun signers(self: &WeightedSigners): &vector { + &self.signers +} + +public(package) fun threshold(self: &WeightedSigners): u128 { + self.threshold +} + +public(package) fun nonce(self: &WeightedSigners): Bytes32 { + self.nonce +} + +/// ----- +/// Internal Functions +/// ----- + +/// Validates the order of the signers and the length of the signers. +/// The signers must be in ascending order by their public key. +/// Otherwise, the error `EInvalidSignersLength` is raised. +fun validate_signers(self: &WeightedSigners) { + assert!(!self.signers.is_empty(), EInvalidSignersLength); + let mut previous = &weighted_signer::default(); + self.signers.do_ref!(|signer| { + signer.validate(); + assert!(previous.lt(signer), EInvalidSignerOrder); + previous = signer; + }); +} + +/// Calculates the total weight of the signers. +fun total_weight(self: &WeightedSigners): u128 { + self + .signers + .fold!(0, |acc, signer| acc + signer.weight()) +} + +/// Validates the threshold. +/// The threshold must be greater than zero and less than or equal to the total +/// weight of the signers. +/// Otherwise, the error `EInvalidThreshold` is raised. +fun validate_threshold(self: &WeightedSigners) { + assert!( + self.threshold != 0 && self.total_weight() >= self.threshold, + EInvalidThreshold, + ); +} + +#[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 mut rng = sui::random::new_generator_for_testing(); + let pub_key = rng.generate_bytes(33); + let signer = axelar_gateway::weighted_signer::new( + pub_key, + rng.generate_u128(), + ); + let nonce = bytes32::from_bytes(rng.generate_bytes(32)); + let threshold = signer.weight(); + WeightedSigners { + signers: vector[signer], + threshold, + nonce, + } +} + +#[test] +fun tent_nonce() { + let weighted_signers = dummy(); + assert!(weighted_signers.nonce() == weighted_signers.nonce); +} + +#[test] +#[expected_failure(abort_code = EInvalidSignersLength)] +fun test_peel_invalid_signers_length() { + let mut rng = sui::random::new_generator_for_testing(); + let mut bcs = bcs::new( + bcs::to_bytes( + &WeightedSigners { + signers: vector[], + threshold: rng.generate_u128(), + nonce: bytes32::from_bytes(rng.generate_bytes(32)), + }, + ), + ); + peel(&mut bcs); +} + +#[test] +#[expected_failure(abort_code = EInvalidSignersLength)] +fun test_validate_signers_invalid_signers_length() { + let mut rng = sui::random::new_generator_for_testing(); + WeightedSigners { + signers: vector[], + threshold: rng.generate_u128(), + nonce: bytes32::from_bytes(rng.generate_bytes(32)), + }.validate_signers(); +} + +#[test] +#[expected_failure(abort_code = EInvalidSignerOrder)] +fun test_validate_signers_invalid_signer_order() { + let mut rng = sui::random::new_generator_for_testing(); + let mut pub_key = @0x0.to_bytes(); + pub_key.push_back(2); + let signer1 = axelar_gateway::weighted_signer::new(pub_key, 1); + pub_key = @0x0.to_bytes(); + pub_key.push_back(1); + let signer2 = axelar_gateway::weighted_signer::new(pub_key, 1); + WeightedSigners { + signers: vector[signer1, signer2], + threshold: rng.generate_u128(), + nonce: bytes32::from_bytes(rng.generate_bytes(32)), + }.validate_signers(); +} + +#[test] +#[expected_failure(abort_code = EInvalidThreshold)] +fun test_validate_zero_threshold() { + let mut rng = sui::random::new_generator_for_testing(); + WeightedSigners { + signers: vector[], + threshold: 0, + nonce: bytes32::from_bytes(rng.generate_bytes(32)), + }.validate_threshold(); +} + +#[test] +#[expected_failure(abort_code = EInvalidThreshold)] +fun test_validate_threshold_above_weight_sum() { + let mut rng = sui::random::new_generator_for_testing(); + let pub_key = rng.generate_bytes(33); + let signer = axelar_gateway::weighted_signer::new(pub_key, 1); + WeightedSigners { + signers: vector[signer], + threshold: 2, + nonce: bytes32::from_bytes(rng.generate_bytes(32)), + }.validate_threshold(); +} diff --git a/move/axelar_gateway_v1/sources/versioned/gateway_v0.move b/move/axelar_gateway_v1/sources/versioned/gateway_v0.move new file mode 100644 index 00000000..4bf5d732 --- /dev/null +++ b/move/axelar_gateway_v1/sources/versioned/gateway_v0.move @@ -0,0 +1,560 @@ +module axelar_gateway::gateway_v0; + +use axelar_gateway::auth::AxelarSigners; +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::channel::{Self, ApprovedMessage}; +use axelar_gateway::events; +use axelar_gateway::message::{Self, Message}; +use axelar_gateway::message_status::{Self, MessageStatus}; +use axelar_gateway::message_ticket::MessageTicket; +use axelar_gateway::proof; +use axelar_gateway::weighted_signers; +use std::ascii::String; +use sui::address; +use sui::clock::Clock; +use sui::hash; +use sui::table::{Self, Table}; +use utils::utils; +use version_control::version_control::VersionControl; + +// ------ +// Errors +// ------ +#[error] +const EMessageNotApproved: vector = + b"trying to `take_approved_message` for a message that is not approved"; + +#[error] +const EZeroMessages: vector = b"no messages found"; + +#[error] +const ENotLatestSigners: vector = b"not latest signers"; + +#[error] +const ENewerMessage: vector = + b"message ticket created from newer versions cannot be sent here"; + +#[error] +const ECannotMigrateTwice: vector = + b"attempting to migrate a even though migration is complete"; + +// ----- +// Types +// ----- +/// An object holding the state of the Axelar bridge. +/// The central piece in managing call approval creation and signature +/// verification. +public struct Gateway_v0 has store { + operator: address, + messages: Table, + signers: AxelarSigners, + version_control: VersionControl, +} + +public enum CommandType { + ApproveMessages, + RotateSigners, +} + +// ----------------- +// Package Functions +// ----------------- +/// Init the module by giving a CreatorCap to the sender to allow a full +/// `setup`. +public(package) fun new( + operator: address, + messages: Table, + signers: AxelarSigners, + version_control: VersionControl, +): Gateway_v0 { + Gateway_v0 { + operator, + messages, + signers, + version_control, + } +} + +public(package) fun version_control(self: &Gateway_v0): &VersionControl { + &self.version_control +} + +public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl) { + assert!(self.version_control.allowed_functions().length() == version_control.allowed_functions().length() - 1, ECannotMigrateTwice ); + self.version_control = version_control; +} + +public(package) fun approve_messages( + self: &mut Gateway_v0, + message_data: vector, + proof_data: vector, +) { + let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); + let messages = peel_messages(message_data); + + let _ = self + .signers + .validate_proof( + data_hash(CommandType::ApproveMessages, message_data), + proof, + ); + + messages.do!(|message| self.approve_message(message)); +} + +public(package) fun rotate_signers( + self: &mut Gateway_v0, + clock: &Clock, + new_signers_data: vector, + proof_data: vector, + ctx: &TxContext, +) { + let weighted_signers = utils::peel!( + new_signers_data, + |bcs| weighted_signers::peel(bcs), + ); + let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); + + let enforce_rotation_delay = ctx.sender() != self.operator; + + let is_latest_signers = self + .signers + .validate_proof( + data_hash(CommandType::RotateSigners, new_signers_data), + proof, + ); + assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); + + // This will fail if signers are duplicated + self + .signers + .rotate_signers(clock, weighted_signers, enforce_rotation_delay); +} + +public(package) fun is_message_approved( + self: &Gateway_v0, + 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[command_id] == message_status::approved(message.hash()) +} + +public(package) fun is_message_executed( + self: &Gateway_v0, + source_chain: String, + message_id: String, +): bool { + let command_id = message::message_to_command_id( + source_chain, + message_id, + ); + + self[command_id] == message_status::executed() +} + +/// 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(package) fun take_approved_message( + self: &mut Gateway_v0, + 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[command_id] == message_status::approved(message.hash()), + EMessageNotApproved, + ); + + let message_status_ref = &mut self[command_id]; + *message_status_ref = message_status::executed(); + + events::message_executed( + message, + ); + + channel::create_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ) +} + +public(package) fun send_message( + _self: &Gateway_v0, + message: MessageTicket, + current_version: u64, +) { + let ( + source_id, + destination_chain, + destination_address, + payload, + version, + ) = message.destroy(); + + assert!(version <= current_version, ENewerMessage); + + events::contract_call( + source_id, + destination_chain, + destination_address, + payload, + address::from_bytes(hash::keccak256(&payload)), + ); +} + +// ----------------- +// Private Functions +// ----------------- + +#[syntax(index)] +fun borrow(self: &Gateway_v0, command_id: Bytes32): &MessageStatus { + table::borrow(&self.messages, command_id) +} + +#[syntax(index)] +fun borrow_mut(self: &mut Gateway_v0, command_id: Bytes32): &mut MessageStatus { + table::borrow_mut(&mut self.messages, command_id) +} + +fun peel_messages(message_data: vector): vector { + utils::peel!(message_data, |bcs| { + let messages = vector::tabulate!( + bcs.peel_vec_length(), + |_| message::peel(bcs), + ); + assert!(messages.length() > 0, EZeroMessages); + messages + }) +} + +fun data_hash(command_type: CommandType, data: vector): Bytes32 { + let mut typed_data = vector::singleton(command_type.as_u8()); + typed_data.append(data); + + bytes32::from_bytes(hash::keccak256(&typed_data)) +} + +fun approve_message(self: &mut Gateway_v0, message: message::Message) { + let command_id = message.command_id(); + + // If the message was already approved, ignore it. + if (self.messages.contains(command_id)) { + return + }; + + self + .messages + .add( + command_id, + message_status::approved(message.hash()), + ); + + events::message_approved( + message, + ); +} + +fun as_u8(self: CommandType): u8 { + match (self) { + CommandType::ApproveMessages => 0, + CommandType::RotateSigners => 1, + } +} + +/// --------- +/// Test Only +/// --------- +#[test_only] +use axelar_gateway::weighted_signers::WeightedSigners; +#[test_only] +use sui::bcs; + +#[test_only] +public(package) fun messages_mut( + self: &mut Gateway_v0, +): &mut Table { + &mut self.messages +} + +#[test_only] +public(package) fun signers_mut(self: &mut Gateway_v0): &mut AxelarSigners { + &mut self.signers +} + +#[test_only] +public(package) fun destroy_for_testing( + self: Gateway_v0, +): (address, Table, AxelarSigners, VersionControl) { + let Gateway_v0 { + operator, + messages, + signers, + version_control, + } = self; + (operator, messages, signers, version_control) +} + +#[test_only] +fun dummy(ctx: &mut TxContext): Gateway_v0 { + new( + @0x0, + sui::table::new(ctx), + axelar_gateway::auth::dummy(ctx), + version_control::version_control::new(vector[]), + ) +} + +#[test_only] +public(package) fun approve_messages_data_hash( + messages: vector, +): Bytes32 { + data_hash(CommandType::ApproveMessages, bcs::to_bytes(&messages)) +} + +#[test_only] +public(package) fun rotate_signers_data_hash( + weighted_signers: WeightedSigners, +): Bytes32 { + data_hash(CommandType::RotateSigners, bcs::to_bytes(&weighted_signers)) +} + +#[test_only] +public(package) fun approve_message_for_testing( + self: &mut Gateway_v0, + message: Message, +) { + self.approve_message(message); +} + +/// ----- +/// Tests +/// ----- +#[test] +#[expected_failure(abort_code = EZeroMessages)] +fun test_peel_messages_no_zero_messages() { + peel_messages(sui::bcs::to_bytes(&vector[])); +} + +#[test] +fun test_approve_message() { + let mut rng = sui::random::new_generator_for_testing(); + 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 = rng.generate_bytes(32); + let payload_hash = axelar_gateway::bytes32::new( + sui::address::from_bytes(hash::keccak256(&payload)), + ); + + let message = message::new( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ); + + let mut data = dummy(ctx); + + data.approve_message(message); + // The second approve message should do nothing. + data.approve_message(message); + + assert!( + data.is_message_approved( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ) == + true, + EMessageNotApproved, + ); + + let approved_message = data.take_approved_message( + source_chain, + message_id, + source_address, + channel.to_address(), + payload, + ); + + channel.consume_approved_message(approved_message); + + assert!( + data.is_message_approved( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ) == + false, + EMessageNotApproved, + ); + + assert!( + data.is_message_executed( + source_chain, + message_id, + ) == + true, + EMessageNotApproved, + ); + + data.messages.remove(message.command_id()); + + sui::test_utils::destroy(data); + 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 = sui::bcs::to_bytes(&vector[message1, message2]); + + let messages = peel_messages(bytes); + + assert!(messages.length() == 2); + assert!(messages[0] == message1); + assert!(messages[1] == message2); +} + +#[test] +#[expected_failure] +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 = sui::bcs::to_bytes(&vector[message1]); + bytes.push_back(0); + + peel_messages(bytes); +} + +#[test] +fun test_command_type_as_u8() { + // Note: These must not be changed to avoid breaking Amplifier integration + assert!(CommandType::ApproveMessages.as_u8() == 0); + assert!(CommandType::RotateSigners.as_u8() == 1); +} + +#[test] +fun test_data_hash() { + let mut rng = sui::random::new_generator_for_testing(); + let data = rng.generate_bytes(32); + let mut typed_data = vector::singleton(CommandType::ApproveMessages.as_u8()); + typed_data.append(data); + + assert!( + data_hash(CommandType::ApproveMessages, data) == + bytes32::from_bytes(hash::keccak256(&typed_data)), + EMessageNotApproved, + ); +} + +#[test] +#[expected_failure(abort_code = ENewerMessage)] +fun test_send_message_newer_message() { + let mut rng = sui::random::new_generator_for_testing(); + let source_id = address::from_u256(rng.generate_u256()); + let destination_chain = std::ascii::string(b"Destination Chain"); + let destination_address = std::ascii::string(b"Destination Address"); + let payload = rng.generate_bytes(32); + let version = 1; + let message = axelar_gateway::message_ticket::new( + source_id, + destination_chain, + destination_address, + payload, + version, + ); + let ctx = &mut sui::tx_context::dummy(); + let self = dummy(ctx); + self.send_message(message, 0); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EMessageNotApproved)] +fun test_take_approved_message_message_not_approved() { + let mut rng = sui::random::new_generator_for_testing(); + let destination_id = address::from_u256(rng.generate_u256()); + let source_chain = std::ascii::string(b"Source Chain"); + let source_address = std::ascii::string(b"Source Address"); + let message_id = std::ascii::string(b"Message Id"); + let payload = rng.generate_bytes(32); + let command_id = message::message_to_command_id(source_chain, message_id); + + let ctx = &mut sui::tx_context::dummy(); + let mut self = dummy(ctx); + + self + .messages + .add( + command_id, + message_status::executed(), + ); + + let approved_message = self.take_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ); + sui::test_utils::destroy(self); + sui::test_utils::destroy(approved_message); +} From 3b141783cf06e7e910ac49c2530388513df5f443 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 21 Nov 2024 19:40:41 +0200 Subject: [PATCH 14/26] renaming axelar_gateway_v1 to axelar_gateway --- move/axelar_gateway/sources/gateway.move | 17 +- .../sources/versioned/gateway_v0.move | 9 + move/axelar_gateway_v1/Move.lock | 45 - move/axelar_gateway_v1/Move.toml | 14 - move/axelar_gateway_v1/sources/auth.move | 291 ----- move/axelar_gateway_v1/sources/channel.move | 311 ----- move/axelar_gateway_v1/sources/events.move | 101 -- move/axelar_gateway_v1/sources/gateway.move | 1075 ----------------- .../sources/types/bytes32.move | 80 -- .../sources/types/message.move | 95 -- .../sources/types/message_status.move | 26 - .../sources/types/message_ticket.move | 135 --- .../sources/types/proof.move | 356 ------ .../sources/types/weighted_signer.move | 162 --- .../sources/types/weighted_signers.move | 207 ---- .../sources/versioned/gateway_v0.move | 560 --------- 16 files changed, 25 insertions(+), 3459 deletions(-) delete mode 100644 move/axelar_gateway_v1/Move.lock delete mode 100644 move/axelar_gateway_v1/Move.toml delete mode 100644 move/axelar_gateway_v1/sources/auth.move delete mode 100644 move/axelar_gateway_v1/sources/channel.move delete mode 100644 move/axelar_gateway_v1/sources/events.move delete mode 100644 move/axelar_gateway_v1/sources/gateway.move delete mode 100644 move/axelar_gateway_v1/sources/types/bytes32.move delete mode 100644 move/axelar_gateway_v1/sources/types/message.move delete mode 100644 move/axelar_gateway_v1/sources/types/message_status.move delete mode 100644 move/axelar_gateway_v1/sources/types/message_ticket.move delete mode 100644 move/axelar_gateway_v1/sources/types/proof.move delete mode 100644 move/axelar_gateway_v1/sources/types/weighted_signer.move delete mode 100644 move/axelar_gateway_v1/sources/types/weighted_signers.move delete mode 100644 move/axelar_gateway_v1/sources/versioned/gateway_v0.move diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index fe2ebcac..6f0d7b84 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -52,7 +52,7 @@ use version_control::version_control::{Self, VersionControl}; // ------- // Version // ------- -const VERSION: u64 = 0; +const VERSION: u64 = 1; // ------- // Structs @@ -185,6 +185,13 @@ entry fun rotate_signers( ) } +/// This function should only be called once +/// (checks should be made on versioned to ensure this) +/// It upgrades the version control to the new version control. +entry fun migrate(self: &mut Gateway) { + self.inner.load_value_mut().migrate(version_control()); +} + // ---------------- // Public Functions // ---------------- @@ -267,6 +274,13 @@ public fun take_approved_message( fun version_control(): VersionControl { version_control::new(vector[ + vector[ + b"approve_messages", + b"rotate_signers", + b"is_message_approved", + b"is_message_executed", + b"take_approved_message", + ].map!(|function_name| function_name.to_ascii_string()), vector[ b"approve_messages", b"rotate_signers", @@ -331,6 +345,7 @@ fun dummy(ctx: &mut TxContext): Gateway { table::new(ctx), auth::dummy(ctx), version_control::new(vector[ + vector[], vector[ b"approve_messages", b"rotate_signers", diff --git a/move/axelar_gateway/sources/versioned/gateway_v0.move b/move/axelar_gateway/sources/versioned/gateway_v0.move index 15193647..4bf5d732 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v0.move +++ b/move/axelar_gateway/sources/versioned/gateway_v0.move @@ -34,6 +34,10 @@ const ENotLatestSigners: vector = b"not latest signers"; const ENewerMessage: vector = b"message ticket created from newer versions cannot be sent here"; +#[error] +const ECannotMigrateTwice: vector = + b"attempting to migrate a even though migration is complete"; + // ----- // Types // ----- @@ -75,6 +79,11 @@ public(package) fun version_control(self: &Gateway_v0): &VersionControl { &self.version_control } +public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl) { + assert!(self.version_control.allowed_functions().length() == version_control.allowed_functions().length() - 1, ECannotMigrateTwice ); + self.version_control = version_control; +} + public(package) fun approve_messages( self: &mut Gateway_v0, message_data: vector, diff --git a/move/axelar_gateway_v1/Move.lock b/move/axelar_gateway_v1/Move.lock deleted file mode 100644 index 722cda48..00000000 --- a/move/axelar_gateway_v1/Move.lock +++ /dev/null @@ -1,45 +0,0 @@ -# @generated by Move, please check-in and do not edit manually. - -[move] -version = 3 -manifest_digest = "E9A0CD1400D2AFF354F91244ACA6D96CD91319A47BFBF47CDCC0347338FD41D2" -deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C" -dependencies = [ - { id = "MoveStdlib", name = "MoveStdlib" }, - { id = "Sui", name = "Sui" }, - { id = "Utils", name = "Utils" }, - { id = "VersionControl", name = "VersionControl" }, -] - -[[move.package]] -id = "MoveStdlib" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "mainnet-v1.35.2", subdir = "crates/sui-framework/packages/move-stdlib" } - -[[move.package]] -id = "Sui" -source = { git = "https://github.com/MystenLabs/sui.git", rev = "mainnet-v1.35.2", subdir = "crates/sui-framework/packages/sui-framework" } - -dependencies = [ - { id = "MoveStdlib", name = "MoveStdlib" }, -] - -[[move.package]] -id = "Utils" -source = { local = "../utils" } - -dependencies = [ - { id = "Sui", name = "Sui" }, -] - -[[move.package]] -id = "VersionControl" -source = { local = "../version_control" } - -dependencies = [ - { id = "Sui", name = "Sui" }, -] - -[move.toolchain-version] -compiler-version = "1.35.2" -edition = "2024.beta" -flavor = "sui" diff --git a/move/axelar_gateway_v1/Move.toml b/move/axelar_gateway_v1/Move.toml deleted file mode 100644 index 86029da2..00000000 --- a/move/axelar_gateway_v1/Move.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "AxelarGateway" -version = "0.1.0" -edition = "2024.beta" - -[dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet-v1.35.2" } -MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "mainnet-v1.35.2" } -VersionControl = { local = "../version_control" } -Utils = { local = "../utils" } - -[addresses] -axelar_gateway = "0xa1" -clock = "0x6" diff --git a/move/axelar_gateway_v1/sources/auth.move b/move/axelar_gateway_v1/sources/auth.move deleted file mode 100644 index 295c338f..00000000 --- a/move/axelar_gateway_v1/sources/auth.move +++ /dev/null @@ -1,291 +0,0 @@ -module axelar_gateway::auth; - -use axelar_gateway::bytes32::{Self, Bytes32}; -use axelar_gateway::events; -use axelar_gateway::proof::Proof; -use axelar_gateway::weighted_signers::WeightedSigners; -use sui::bcs; -use sui::clock::Clock; -use sui::table::{Self, Table}; - -// ------ -// Errors -// ------ -#[error] -const EInsufficientRotationDelay: vector = b"insufficient rotation delay"; - -#[error] -const EInvalidEpoch: vector = - b"the difference between current_epoch and signers_epoch exceeds the allowed retention period"; - -// ----- -// 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, -} - -// ----------------- -// 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 -} - -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, - ); - - proof.validate( - bcs::to_bytes( - &MessageToSign { - domain_separator: self.domain_separator, - signers_hash, - data_hash, - }, - ), - ); - - is_latest_signers -} - -public(package) fun rotate_signers( - self: &mut AxelarSigners, - clock: &Clock, - new_signers: WeightedSigners, - enforce_rotation_delay: bool, -) { - new_signers.validate(); - - 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; - - events::signers_rotated( - epoch, - new_signers_hash, - new_signers, - ); -} - -// ------------------ -// Internal Functions -// ------------------ -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 -// --------- -#[test_only] -use sui::ecdsa_k1; -#[test_only] -use axelar_gateway::proof; - -#[test_only] -public fun dummy(ctx: &mut TxContext): AxelarSigners { - let mut rng = sui::random::new_generator_for_testing(); - AxelarSigners { - epoch: 0, - epoch_by_signers_hash: table::new(ctx), - domain_separator: bytes32::from_bytes(rng.generate_bytes(32)), - minimum_rotation_delay: 1, - last_rotation_timestamp: 0, - previous_signers_retention: 3, - } -} - -#[test_only] -public(package) fun new_message_to_sign( - domain_separator: Bytes32, - signers_hash: Bytes32, - data_hash: Bytes32, -): MessageToSign { - MessageToSign { - domain_separator, - signers_hash, - data_hash, - } -} - -#[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(package) fun epoch_mut(self: &mut AxelarSigners): &mut u64 { - &mut self.epoch -} - -#[test_only] -public(package) fun generate_proof( - data_hash: Bytes32, - domain_separator: Bytes32, - weighted_signers: WeightedSigners, - keypairs: &vector, -): Proof { - let message_to_sign = bcs::to_bytes( - &MessageToSign { - domain_separator, - signers_hash: weighted_signers.hash(), - data_hash, - }, - ); - - proof::generate(weighted_signers, &message_to_sign, keypairs) -} - -// ----- -// Tests -// ----- -#[test] -fun test_new() { - let ctx = &mut sui::tx_context::dummy(); - let self = new(ctx); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure(abort_code = EInsufficientRotationDelay)] -fun test_update_rotation_timestamp_insufficient_rotation_delay() { - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - - let clock = sui::clock::create_for_testing(ctx); - - self.last_rotation_timestamp = 1; - - self.update_rotation_timestamp(&clock, true); - - sui::test_utils::destroy(self); - clock.destroy_for_testing(); -} - -#[test] -#[expected_failure(abort_code = EInvalidEpoch)] -fun test_validate_proof_invalid_epoch() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let mut self = new(ctx); - let proof = axelar_gateway::proof::create_for_testing( - axelar_gateway::weighted_signers::create_for_testing( - vector[], - 0, - bytes32::from_bytes(rng.generate_bytes(32)), - ), - vector[], - ); - let signers = proof.signers(); - let signers_hash = signers.hash(); - let signers_epoch = 0; - self.epoch_by_signers_hash.add(signers_hash, signers_epoch); - - self.previous_signers_retention = 2; - self.epoch = 3; - - self.validate_proof( - bytes32::from_bytes(rng.generate_bytes(32)), - proof, - ); - - sui::test_utils::destroy(self); -} diff --git a/move/axelar_gateway_v1/sources/channel.move b/move/axelar_gateway_v1/sources/channel.move deleted file mode 100644 index 45ad15bd..00000000 --- a/move/axelar_gateway_v1/sources/channel.move +++ /dev/null @@ -1,311 +0,0 @@ -/// Channels -/// -/// 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 axelar_gateway::events; -use std::ascii::String; - -// ----- -// 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, -} - -/// 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, -} - -// ------ -// Errors -// ------ - -/// If approved message is consumed by an invalid destination id -#[error] -const EInvalidDestination: vector = b"invalid destination"; - -// ---------------- -// Public Functions -// ---------------- - -/// 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); - - events::channel_created(id.uid_to_address()); - - Channel { - id, - } -} - -/// 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; - - events::channel_destroyed(id.uid_to_address()); - - id.delete(); -} - -public fun id(self: &Channel): ID { - object::id(self) -} - -public fun to_address(self: &Channel): address { - object::id_address(self) -} - -/// 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) -} - -// ----------------- -// 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 Only -// --------- -#[test_only] -use utils::utils; - -#[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_only] -public fun destroy_for_testing(approved_message: ApprovedMessage) { - ApprovedMessage { - source_chain: _, - message_id: _, - source_address: _, - destination_id: _, - payload: _, - } = approved_message; -} - -#[test_only] -public(package) fun approved_message_source_chain( - self: &ApprovedMessage, -): String { - self.source_chain -} - -#[test_only] -public(package) fun approved_message_message_id( - self: &ApprovedMessage, -): String { - self.message_id -} - -#[test_only] -public(package) fun approved_message_source_address( - self: &ApprovedMessage, -): String { - self.source_address -} - -#[test_only] -public(package) fun approved_message_destination_id( - self: &ApprovedMessage, -): address { - self.destination_id -} - -#[test_only] -public(package) fun approved_message_payload( - self: &ApprovedMessage, -): vector { - self.payload -} - -// ----- -// Tests -// ----- -#[test] -fun test_new_and_destroy() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - utils::assert_event(); - channel.destroy(); - utils::assert_event(); -} - -#[test] -fun test_id() { - let ctx = &mut sui::tx_context::dummy(); - let channel: Channel = new(ctx); - assert!(channel.id() == object::id(&channel)); - 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)); - channel.destroy() -} - -#[test] -fun test_create_approved_message() { - let mut rng = sui::random::new_generator_for_testing(); - 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 = sui::address::from_bytes(rng.generate_bytes(32)); - let input_payload = rng.generate_bytes(32); - 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); - assert!(message_id == input_message_id); - assert!(source_address == input_source_address); - assert!(destination_id == input_destination_id); - assert!(payload == input_payload); -} - -#[test] -fun test_consume_approved_message() { - let mut rng = sui::random::new_generator_for_testing(); - 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 = rng.generate_bytes(32); - 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); - assert!(message_id == input_message_id); - assert!(source_address == input_source_address); - assert!(payload == input_payload); - - channel.destroy(); -} - -#[test] -#[expected_failure(abort_code = EInvalidDestination)] -fun test_consume_approved_message_wrong_destination() { - let mut rng = sui::random::new_generator_for_testing(); - 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 = sui::address::from_bytes(rng.generate_bytes(32)); - let payload = rng.generate_bytes(32); - - 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_v1/sources/events.move b/move/axelar_gateway_v1/sources/events.move deleted file mode 100644 index 29247a9b..00000000 --- a/move/axelar_gateway_v1/sources/events.move +++ /dev/null @@ -1,101 +0,0 @@ -module axelar_gateway::events; - -use axelar_gateway::bytes32::Bytes32; -use axelar_gateway::message::Message; -use axelar_gateway::weighted_signers::WeightedSigners; -use std::ascii::String; -use sui::event; - -// ------ -// Events -// ------ -/// Emitted when signers are rotated. -public struct SignersRotated has copy, drop { - epoch: u64, - signers_hash: Bytes32, - signers: WeightedSigners, -} - -/// Emitted when a new channel is created. -public struct ChannelCreated has copy, drop { - id: address, -} - -/// Emitted when a channel is destroyed. -public struct ChannelDestroyed has copy, drop { - id: address, -} - -/// 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, -} - -/// Emitted when a message is taken to be executed by a channel. -public struct MessageExecuted has copy, drop { - message: Message, -} - -// ----------------- -// Package Functions -// ----------------- -public(package) fun signers_rotated( - epoch: u64, - signers_hash: Bytes32, - signers: WeightedSigners, -) { - event::emit(SignersRotated { - epoch, - signers_hash, - signers, - }); -} - -public(package) fun channel_created(id: address) { - event::emit(ChannelCreated { - id, - }); -} - -public(package) fun channel_destroyed(id: address) { - event::emit(ChannelDestroyed { - id, - }); -} - -public(package) fun contract_call( - source_id: address, - destination_chain: String, - destination_address: String, - payload: vector, - payload_hash: address, -) { - event::emit(ContractCall { - source_id, - destination_chain, - destination_address, - payload, - payload_hash, - }); -} - -public(package) fun message_approved(message: Message) { - event::emit(MessageApproved { - message, - }); -} - -public(package) fun message_executed(message: Message) { - event::emit(MessageExecuted { - message, - }); -} diff --git a/move/axelar_gateway_v1/sources/gateway.move b/move/axelar_gateway_v1/sources/gateway.move deleted file mode 100644 index 6f0d7b84..00000000 --- a/move/axelar_gateway_v1/sources/gateway.move +++ /dev/null @@ -1,1075 +0,0 @@ -/// Implementation of the Axelar Gateway for Sui Move. -/// -/// This code is based on the following: -/// -/// - When call approvals is sent to Sui, it targets an object and not a module; -/// - To support cross-chain messaging, a Channel object has to be created; -/// - Channel can be either owned or shared but not frozen; -/// - Module developer on the Sui side will have to implement a system to -/// support messaging; -/// - Checks for uniqueness of approvals should be done through `Channel`s to -/// avoid big value storage; -/// -/// I. Sending call approvals -/// -/// A approval is sent through the `send` function, a Channel is supplied to -/// determine the source -> ID. -/// Event is then emitted and Axelar network can operate -/// -/// II. Receiving call approvals -/// -/// Approval bytes and signatures are passed into `create` function to generate -/// a CallApproval object. -/// - Signatures are checked against the known set of signers. -/// - CallApproval bytes are parsed to determine: source, destination_chain, -/// payload and destination_id -/// - `destination_id` points to a `Channel` object -/// -/// Once created, `CallApproval` needs to be consumed. And the only way to do it -/// is by calling -/// `consume_call_approval` function and pass a correct `Channel` instance -/// alongside the `CallApproval`. -/// - CallApproval is checked for uniqueness (for this channel) -/// - CallApproval is checked to match the `Channel`.id -/// -/// The Gateway object uses a versioned field to support upgradability. The -/// current implementation uses Gateway_v0. -module axelar_gateway::gateway; - -use axelar_gateway::auth::{Self, validate_proof}; -use axelar_gateway::bytes32::{Self, Bytes32}; -use axelar_gateway::channel::{Channel, ApprovedMessage}; -use axelar_gateway::gateway_v0::{Self, Gateway_v0}; -use axelar_gateway::message_ticket::{Self, MessageTicket}; -use axelar_gateway::weighted_signers; -use std::ascii::{Self, String}; -use sui::clock::Clock; -use sui::table; -use sui::versioned::{Self, Versioned}; -use utils::utils; -use version_control::version_control::{Self, VersionControl}; - -// ------- -// Version -// ------- -const VERSION: u64 = 1; - -// ------- -// Structs -// ------- -public struct Gateway has key { - id: UID, - inner: Versioned, -} - -// ------------ -// Capabilities -// ------------ -public struct CreatorCap has key, store { - id: UID, -} - -// ----- -// Setup -// ----- - -/// Init the module by giving a CreatorCap to the sender to allow a full -/// `setup`. -fun init(ctx: &mut TxContext) { - let cap = CreatorCap { - id: object::new(ctx), - }; - - transfer::transfer(cap, ctx.sender()); -} - -/// Setup the module by creating a new Gateway object. -entry fun setup( - cap: CreatorCap, - operator: address, - domain_separator: address, - minimum_rotation_delay: u64, - previous_signers_retention: u64, - initial_signers: vector, - clock: &Clock, - ctx: &mut TxContext, -) { - let CreatorCap { id } = cap; - id.delete(); - - let inner = versioned::create( - VERSION, - gateway_v0::new( - operator, - table::new(ctx), - auth::setup( - bytes32::new(domain_separator), - minimum_rotation_delay, - previous_signers_retention, - utils::peel!( - initial_signers, - |bcs| weighted_signers::peel(bcs), - ), - clock, - ctx, - ), - version_control(), - ), - ctx, - ); - - // Share the gateway object for anyone to use. - transfer::share_object(Gateway { - id: object::new(ctx), - inner, - }); -} - -// ------ -// Macros -// ------ -/// This macro also uses version control to sinplify things a bit. -macro fun value($self: &Gateway, $function_name: vector): &Gateway_v0 { - let gateway = $self; - let value = gateway.inner.load_value(); - value.version_control().check(VERSION, ascii::string($function_name)); - value -} - -/// This macro also uses version control to sinplify things a bit. -macro fun value_mut( - $self: &mut Gateway, - $function_name: vector, -): &mut Gateway_v0 { - let gateway = $self; - let value = gateway.inner.load_value_mut(); - value.version_control().check(VERSION, ascii::string($function_name)); - value -} - -// ----------- -// 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 value = self.value_mut!(b"approve_messages"); - value.approve_messages(message_data, proof_data); -} - -/// 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 value = self.value_mut!(b"rotate_signers"); - value.rotate_signers( - clock, - new_signers_data, - proof_data, - ctx, - ) -} - -/// This function should only be called once -/// (checks should be made on versioned to ensure this) -/// It upgrades the version control to the new version control. -entry fun migrate(self: &mut Gateway) { - self.inner.load_value_mut().migrate(version_control()); -} - -// ---------------- -// Public Functions -// ---------------- - -/// Prepare a MessageTicket to call a contract on the destination chain. -public fun prepare_message( - channel: &Channel, - destination_chain: String, - destination_address: String, - payload: vector, -): MessageTicket { - message_ticket::new( - channel.to_address(), - destination_chain, - destination_address, - payload, - VERSION, - ) -} - -/// Submit the MessageTicket which causes a contract call by sending an event -/// from an -/// authorized Channel. -public fun send_message(self: &Gateway, message: MessageTicket) { - let value = self.value!(b"send_message"); - value.send_message(message, VERSION); -} - -public fun is_message_approved( - self: &Gateway, - source_chain: String, - message_id: String, - source_address: String, - destination_id: address, - payload_hash: Bytes32, -): bool { - let value = self.value!(b"is_message_approved"); - value.is_message_approved( - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - ) -} - -public fun is_message_executed( - self: &Gateway, - source_chain: String, - message_id: String, -): bool { - let value = self.value!(b"is_message_executed"); - value.is_message_executed(source_chain, message_id) -} - -/// 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 value = self.value_mut!(b"take_approved_message"); - value.take_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ) -} - -// ----------------- -// Private Functions -// ----------------- - -fun version_control(): VersionControl { - version_control::new(vector[ - vector[ - b"approve_messages", - b"rotate_signers", - b"is_message_approved", - b"is_message_executed", - b"take_approved_message", - ].map!(|function_name| function_name.to_ascii_string()), - vector[ - b"approve_messages", - b"rotate_signers", - b"is_message_approved", - b"is_message_executed", - b"take_approved_message", - b"send_message", - ].map!(|function_name| function_name.to_ascii_string()), - ]) -} - -// --------- -// Test Only -// --------- -#[test_only] -use sui::bcs; -#[test_only] -use axelar_gateway::auth::generate_proof; -#[test_only] -use axelar_gateway::events; - -#[test_only] -public fun create_for_testing( - operator: address, - domain_separator: Bytes32, - minimum_rotation_delay: u64, - previous_signers_retention: u64, - initial_signers: weighted_signers::WeightedSigners, - clock: &Clock, - ctx: &mut TxContext, -): Gateway { - let inner = versioned::create( - VERSION, - gateway_v0::new( - operator, - table::new(ctx), - auth::setup( - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - clock, - ctx, - ), - version_control(), - ), - ctx, - ); - Gateway { - id: object::new(ctx), - inner, - } -} - -#[test_only] -fun dummy(ctx: &mut TxContext): Gateway { - let mut rng = sui::random::new_generator_for_testing(); - let inner = versioned::create( - VERSION, - gateway_v0::new( - sui::address::from_bytes(rng.generate_bytes(32)), - table::new(ctx), - auth::dummy(ctx), - version_control::new(vector[ - vector[], - vector[ - b"approve_messages", - b"rotate_signers", - b"is_message_approved", - b"is_message_executed", - b"take_approved_message", - b"send_message", - b"", - ].map!(|function_name| function_name.to_ascii_string()), - ]), - ), - ctx, - ); - Gateway { - id: object::new(ctx), - inner, - } -} - -#[test_only] -public fun destroy_for_testing(self: Gateway) { - let Gateway { - id, - inner, - } = self; - id.delete(); - - let value = inner.destroy(); - let (_, messages, signers, _) = value.destroy_for_testing(); - - let (_, table, _, _, _, _) = signers.destroy_for_testing(); - table.destroy_empty(); - messages.destroy_empty(); -} - -// ---- -// Test -// ---- -#[test] -fun test_init() { - let mut ts = sui::test_scenario::begin(@0x0); - - init(ts.ctx()); - ts.next_tx(@0x0); - - let creator_cap = ts.take_from_sender(); - ts.return_to_sender(creator_cap); - ts.end(); -} - -#[test] -fun test_setup() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = sui::address::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = axelar_gateway::weighted_signers::dummy(); - let mut clock = sui::clock::create_for_testing(ctx); - let timestamp = rng.generate_u64(); - 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); - - let gateway_id = shared[0]; - let gateway = scenario.take_shared_by_id(gateway_id); - let Gateway { - id, - inner, - } = gateway; - id.delete(); - - let (operator_result, messages, signers, _) = inner - .destroy() - .destroy_for_testing(); - - assert!(operator == operator_result); - 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); - assert!(signer_epoch == 1); - assert!(bytes32::new(domain_separator) == domain_separator_result); - assert!(minimum_rotation_delay == minimum_rotation_delay_result); - assert!(last_rotation_timestamp == timestamp); - assert!(previous_signers_retention == previous_signers_retention_result); - - clock.destroy_for_testing(); - scenario.end(); -} - -#[test] -#[expected_failure] -fun test_setup_remaining_bytes() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = sui::address::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = axelar_gateway::weighted_signers::dummy(); - let mut clock = sui::clock::create_for_testing(ctx); - let timestamp = rng.generate_u64(); - clock.increment_for_testing(timestamp); - - let creator_cap = CreatorCap { - id: object::new(ctx), - }; - - let mut scenario = sui::test_scenario::begin(@0x1); - let mut initial_signers_bytes = bcs::to_bytes(&initial_signers); - initial_signers_bytes.push_back(0); - setup( - creator_cap, - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers_bytes, - &clock, - scenario.ctx(), - ); - - let tx_effects = scenario.next_tx(@0x1); - let shared = tx_effects.shared(); - - assert!(shared.length() == 1); - - let gateway_id = shared[0]; - let gateway = scenario.take_shared_by_id(gateway_id); - let Gateway { - id, - inner, - } = gateway; - id.delete(); - - let (operator_result, messages, signers, _) = inner - .destroy() - .destroy_for_testing(); - - assert!(operator == operator_result); - 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); - assert!(signer_epoch == 1); - assert!(bytes32::new(domain_separator) == domain_separator_result); - assert!(minimum_rotation_delay == minimum_rotation_delay_result); - assert!(last_rotation_timestamp == timestamp); - assert!(previous_signers_retention == previous_signers_retention_result); - - clock.destroy_for_testing(); - scenario.end(); -} - -#[test] -fun test_peel_weighted_signers() { - let signers = axelar_gateway::weighted_signers::dummy(); - let bytes = bcs::to_bytes(&signers); - let result = utils::peel!(bytes, |bcs| weighted_signers::peel(bcs)); - - assert!(result == signers); -} - -#[test] -#[expected_failure] -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); - - utils::peel!(bytes, |bcs| weighted_signers::peel(bcs)); -} - -#[test] -fun test_peel_proof() { - let proof = axelar_gateway::proof::dummy(); - let bytes = bcs::to_bytes(&proof); - let result = utils::peel!(bytes, |bcs| axelar_gateway::proof::peel(bcs)); - - assert!(result == proof); -} - -#[test] -#[expected_failure] -fun test_peel_proof_no_remaining_data() { - let proof = axelar_gateway::proof::dummy(); - let mut bytes = bcs::to_bytes(&proof); - bytes.push_back(0); - - utils::peel!(bytes, |bcs| axelar_gateway::proof::peel(bcs)); -} - -#[test] -fun test_take_approved_message() { - let mut rng = sui::random::new_generator_for_testing(); - let mut gateway = dummy(&mut sui::tx_context::dummy()); - 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 = sui::address::from_bytes(rng.generate_bytes(32)); - let payload = rng.generate_bytes(32); - let payload_hash = axelar_gateway::bytes32::new( - sui::address::from_bytes(sui::hash::keccak256(&payload)), - ); - let message = axelar_gateway::message::new( - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - ); - - gateway - .value_mut!(b"") - .messages_mut() - .add( - message.command_id(), - axelar_gateway::message_status::approved(message.hash()), - ); - - let approved_message = gateway.take_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ); - - utils::assert_event(); - - let expected_approved_message = axelar_gateway::channel::create_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ); - assert!(&approved_message == &expected_approved_message); - - gateway.value_mut!(b"").messages_mut().remove(message.command_id()); - - approved_message.destroy_for_testing(); - expected_approved_message.destroy_for_testing(); - gateway.destroy_for_testing(); -} - -#[test] -fun test_approve_messages() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - let messages = vector[ - axelar_gateway::message::dummy(), - ]; - let data_hash = gateway_v0::approve_messages_data_hash(messages); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - - self.approve_messages(bcs::to_bytes(&messages), bcs::to_bytes(&proof)); - - utils::assert_event(); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self) -} - -#[test] -#[expected_failure] -fun test_approve_messages_remaining_data() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - let messages = vector[axelar_gateway::message::dummy()]; - let data_hash = gateway_v0::approve_messages_data_hash(messages); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - let mut proof_data = bcs::to_bytes(&proof); - proof_data.push_back(0); - - self.approve_messages(bcs::to_bytes(&messages), proof_data); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self) -} - -#[test] -fun test_rotate_signers() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let next_weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ).public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let mut clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - - utils::assert_event(); - - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - - clock.increment_for_testing(minimum_rotation_delay); - self.rotate_signers( - &clock, - bcs::to_bytes(&next_weighted_signers), - bcs::to_bytes(&proof), - ctx, - ); - - utils::assert_events(2); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure] -fun test_rotate_signers_remaining_data_message_data() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let next_weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ).public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let mut clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - - let mut message_data = bcs::to_bytes(&next_weighted_signers); - message_data.push_back(0); - - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - - clock.increment_for_testing(minimum_rotation_delay); - self.rotate_signers(&clock, message_data, bcs::to_bytes(&proof), ctx); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure] -fun test_rotate_signers_remaining_data_proof_data() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let next_weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ).public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let mut clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - let mut proof_data = bcs::to_bytes(&proof); - proof_data.push_back(0); - - clock.increment_for_testing(minimum_rotation_delay); - self.rotate_signers( - &clock, - bcs::to_bytes(&next_weighted_signers), - proof_data, - ctx, - ); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure(abort_code = axelar_gateway::gateway_v0::ENotLatestSigners)] -fun test_rotate_signers_not_latest_signers() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let keypair = sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ); - let weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *keypair.public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - let next_weighted_signers = weighted_signers::create_for_testing( - vector[ - axelar_gateway::weighted_signer::new( - *sui::ecdsa_k1::secp256k1_keypair_from_seed( - &rng.generate_bytes(32), - ).public_key(), - 1, - ), - ], - 1, - bytes32::from_bytes(rng.generate_bytes(32)), - ); - - let operator = sui::address::from_bytes(rng.generate_bytes(32)); - let domain_separator = bytes32::from_bytes(rng.generate_bytes(32)); - let minimum_rotation_delay = rng.generate_u64(); - let previous_signers_retention = rng.generate_u64(); - let initial_signers = weighted_signers; - let mut clock = sui::clock::create_for_testing(ctx); - let mut self = create_for_testing( - operator, - domain_separator, - minimum_rotation_delay, - previous_signers_retention, - initial_signers, - &clock, - ctx, - ); - // Tell the gateway this is not the latest epoch - let epoch = self.value_mut!(b"rotate_signers").signers_mut().epoch_mut(); - *epoch = *epoch + 1; - - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); - let proof = generate_proof( - data_hash, - domain_separator, - weighted_signers, - &vector[keypair], - ); - - clock.increment_for_testing(minimum_rotation_delay); - self.rotate_signers( - &clock, - bcs::to_bytes(&next_weighted_signers), - bcs::to_bytes(&proof), - ctx, - ); - - clock.destroy_for_testing(); - sui::test_utils::destroy(self); -} - -#[test] -fun test_is_message_approved() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - - let source_chain = ascii::string(b"Source Chain"); - let source_address = ascii::string(b"Source Address"); - let message_id = ascii::string(b"Message Id"); - let destination_id = sui::address::from_bytes(rng.generate_bytes(32)); - let payload_hash = bytes32::from_bytes(rng.generate_bytes(32)); - let message = axelar_gateway::message::new( - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - ); - - let mut gateway = dummy(ctx); - gateway.value_mut!(b"").approve_message_for_testing(message); - assert!( - gateway.is_message_approved( - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - ), - ); - assert!( - !gateway.is_message_executed( - source_chain, - message_id, - ), - ); - - sui::test_utils::destroy(gateway); -} - -#[test] -fun test_send_message() { - let mut rng = sui::random::new_generator_for_testing(); - let ctx = &mut sui::tx_context::dummy(); - let channel = axelar_gateway::channel::new(ctx); - let destination_chain = ascii::string(b"Destination Chain"); - let destination_address = ascii::string(b"Destination Address"); - let payload = rng.generate_bytes(32); - let message_ticket = prepare_message( - &channel, - destination_chain, - destination_address, - payload, - ); - - let gateway = dummy(ctx); - gateway.send_message(message_ticket); - - utils::assert_event(); - - sui::test_utils::destroy(gateway); - channel.destroy(); -} diff --git a/move/axelar_gateway_v1/sources/types/bytes32.move b/move/axelar_gateway_v1/sources/types/bytes32.move deleted file mode 100644 index 704175f0..00000000 --- a/move/axelar_gateway_v1/sources/types/bytes32.move +++ /dev/null @@ -1,80 +0,0 @@ -module axelar_gateway::bytes32; - -use sui::address; -use sui::bcs::BCS; - -// ----- -// Types -// ----- - -public struct Bytes32 has copy, drop, store { - bytes: address, -} - -// --------- -// Constants -// --------- - -const LENGTH: u64 = 32; - -// ---------------- -// Public Functions -// ---------------- - -/// Casts an address to a bytes32 -public fun new(bytes: address): Bytes32 { - Bytes32 { bytes: bytes } -} - -public fun default(): Bytes32 { - Bytes32 { bytes: @0x0 } -} - -public fun from_bytes(bytes: vector): Bytes32 { - new(address::from_bytes(bytes)) -} - -public fun from_address(addr: address): Bytes32 { - new(addr) -} - -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()) -} - -// ----- -// Tests -// ----- - -#[test] -public fun test_new() { - let addr = address::from_u256(sui::random::new_generator_for_testing().generate_u256()); - let actual = new(addr); - - assert!(actual.to_bytes() == addr.to_bytes()); - assert!(actual.length() == LENGTH); -} - -#[test] -public fun test_default() { - let default = default(); - - assert!(default.bytes == @0x0); - assert!(default.length() == LENGTH); -} - -#[test] -public fun test_from_address() { - let addr = address::from_u256(sui::random::new_generator_for_testing().generate_u256()); - let bytes32 = from_address(addr); - assert!(bytes32.bytes == addr); - assert!(bytes32.length() == LENGTH); -} diff --git a/move/axelar_gateway_v1/sources/types/message.move b/move/axelar_gateway_v1/sources/types/message.move deleted file mode 100644 index bdd361a0..00000000 --- a/move/axelar_gateway_v1/sources/types/message.move +++ /dev/null @@ -1,95 +0,0 @@ -module axelar_gateway::message; - -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, -} - -/// ----------------- -/// 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); - - 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()); - - 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 hash(self: &Message): Bytes32 { - bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) -} - -// --------- -// Test Only -// --------- -#[test_only] -public(package) fun dummy(): Message { - let source_chain = std::ascii::string(b"Source Chain"); - let source_address = std::ascii::string(b"Source Address"); - let message_id = std::ascii::string(b"Message Id"); - let destination_id = @0x4; - let payload_hash = bytes32::new(@0x5); - Message { - source_chain, - message_id, - source_address, - destination_id, - payload_hash, - } -} diff --git a/move/axelar_gateway_v1/sources/types/message_status.move b/move/axelar_gateway_v1/sources/types/message_status.move deleted file mode 100644 index 796153f7..00000000 --- a/move/axelar_gateway_v1/sources/types/message_status.move +++ /dev/null @@ -1,26 +0,0 @@ -module axelar_gateway::message_status; - -use axelar_gateway::bytes32::Bytes32; - -// ----- -// Types -// ----- -/// The Status of the message. -/// Can be either one of two statuses: -/// - Approved: Set to the hash of the message -/// - Executed: Message was already executed -public enum MessageStatus has copy, drop, store { - Approved(Bytes32), - Executed, -} - -// ----------------- -// Package Functions -// ----------------- -public(package) fun approved(hash: Bytes32): MessageStatus { - MessageStatus::Approved(hash) -} - -public(package) fun executed(): MessageStatus { - MessageStatus::Executed -} diff --git a/move/axelar_gateway_v1/sources/types/message_ticket.move b/move/axelar_gateway_v1/sources/types/message_ticket.move deleted file mode 100644 index 176bf43f..00000000 --- a/move/axelar_gateway_v1/sources/types/message_ticket.move +++ /dev/null @@ -1,135 +0,0 @@ -module axelar_gateway::message_ticket; - -use std::ascii::String; - -// ----- -// Types -// ----- -/// This hot potato object is created to capture all the information about a -/// remote contract call. -/// In can then be submitted to the gateway to send the Message. -/// It is advised that modules return this Message ticket to be submitted by the -/// frontend, so that when the gateway package is upgraded, the app doesn't need -/// to upgrade as well, ensuring forward compatibility. -/// The version is captured to ensure that future packages can restrict which -/// messages they can send, and to ensure that no future messages are sent from -/// earlier versions. -public struct MessageTicket { - source_id: address, - destination_chain: String, - destination_address: String, - payload: vector, - version: u64, -} - -// ------- -// Getters -// ------- -public fun source_id(self: &MessageTicket): address { - self.source_id -} - -public fun destination_chain(self: &MessageTicket): String { - self.destination_chain -} - -public fun destination_address(self: &MessageTicket): String { - self.destination_address -} - -public fun payload(self: &MessageTicket): vector { - self.payload -} - -public fun version(self: &MessageTicket): u64 { - self.version -} - -// ----------------- -// Package Functions -// ----------------- -public(package) fun new( - source_id: address, - destination_chain: String, - destination_address: String, - payload: vector, - version: u64, -): MessageTicket { - MessageTicket { - source_id, - destination_chain, - destination_address, - payload, - version, - } -} - -public(package) fun destroy( - self: MessageTicket, -): (address, String, String, vector, u64) { - let MessageTicket { - source_id, - destination_chain, - destination_address, - payload, - version, - } = self; - (source_id, destination_chain, destination_address, payload, version) -} - -#[test_only] -public fun new_for_testing( - source_id: address, - destination_chain: String, - destination_address: String, - payload: vector, - version: u64, -): MessageTicket { - MessageTicket { - source_id, - destination_chain, - destination_address, - payload, - version, - } -} - -#[test] -fun test_all() { - let mut rng = sui::random::new_generator_for_testing(); - let source_id = sui::address::from_u256(rng.generate_u256()); - let destination_chain = std::ascii::string(b"Destination Chain"); - let destination_address = std::ascii::string( - b"Destination Address", - ); - let payload: vector = rng.generate_bytes(256); - let version: u64 = 2; - - let message_ticket = new( - source_id, - destination_chain, - destination_address, - payload, - version, - ); - - assert!(message_ticket.source_id() == source_id); - assert!(message_ticket.destination_chain() == destination_chain); - assert!(message_ticket.destination_address() == destination_address); - assert!(message_ticket.payload() == payload); - assert!(message_ticket.version() == version); - - let ( - result_source_id, - result_destination_chain, - result_destination_address, - result_payload, - result_version, - ) = message_ticket.destroy(); - - assert!(result_source_id == source_id); - assert!(result_destination_chain == destination_chain); - assert!(result_destination_address == destination_address); - assert!(result_payload == payload); - assert!(result_version == version); -} diff --git a/move/axelar_gateway_v1/sources/types/proof.move b/move/axelar_gateway_v1/sources/types/proof.move deleted file mode 100644 index ac4625de..00000000 --- a/move/axelar_gateway_v1/sources/types/proof.move +++ /dev/null @@ -1,356 +0,0 @@ -module axelar_gateway::proof; - -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, -} - -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 -#[error] -const EInvalidSignatureLength: vector = - b"invalid signature length: expected 65 bytes"; - -#[error] -const ELowSignaturesWeight: vector = b"insufficient signatures weight"; - -#[error] -const ESignerNotFound: vector = - b"no signer found with the specified public key in the given range"; - -#[error] -const ERedundantSignaturesProvided: vector = - b"redundant signatures provided"; - -// ---------------- -// 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 -} - -// ----------------- -// Package Functions -// ----------------- -public(package) fun new_signature(bytes: vector): Signature { - assert!(bytes.length() == SIGNATURE_LENGTH, EInvalidSignatureLength); - - 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) -} - -/// Validates the signatures of a message against the signers. -/// The total weight of the signatures must be greater than or equal to the -/// threshold. -/// Otherwise, the error `ELowSignaturesWeight` is raised. -public(package) fun validate(self: &Proof, message: vector) { - let signers = &self.signers; - let signatures = &self.signatures; - assert!(signatures.length() != 0, ELowSignaturesWeight); - - let threshold = signers.threshold(); - let signatures_length = signatures.length(); - let mut total_weight: u128 = 0; - let mut signer_index = 0; - let mut i = 0; - - while (i < signatures_length) { - let pub_key = signatures[i].recover_pub_key(&message); - - let (weight, index) = find_weight_by_pub_key_from( - signers, - signer_index, - &pub_key, - ); - - total_weight = total_weight + weight; - - if (total_weight >= threshold) { - if (i + 1 == signatures_length) { - return - }; - - abort ERedundantSignaturesProvided - }; - - i = i + 1; - signer_index = index + 1; - }; - - abort ELowSignaturesWeight -} - -/// Finds the weight of a signer in the weighted signers by its public key. -fun find_weight_by_pub_key_from( - signers: &WeightedSigners, - signer_index: u64, - pub_key: &vector, -): (u128, u64) { - let signers = signers.signers(); - let length = signers.length(); - let mut index = signer_index; - - // Find the first signer that satisfies the predicate - while (index < length && signers[index].pub_key() != pub_key) { - index = index + 1; - }; - - // If no signer satisfies the predicate, return an error - assert!(index < length, ESignerNotFound); - - (signers[index].weight(), index) -} - -public(package) fun peel_signature(bcs: &mut BCS): Signature { - let bytes = bcs.peel_vec_u8(); - - new_signature(bytes) -} - -public(package) fun peel(bcs: &mut BCS): Proof { - let signers = weighted_signers::peel(bcs); - let length = bcs.peel_vec_length(); - - Proof { - signers, - signatures: vector::tabulate!(length, |_| peel_signature(bcs)), - } -} - -// --------- -// Test Only -// --------- -#[test_only] -public(package) fun create_for_testing( - signers: WeightedSigners, - signatures: vector, -): Proof { - Proof { - signers, - signatures, - } -} - -#[test_only] -public(package) fun dummy(): Proof { - let mut rng = sui::random::new_generator_for_testing(); - let signature = rng.generate_bytes(SIGNATURE_LENGTH as u16); - Proof { - signers: axelar_gateway::weighted_signers::dummy(), - signatures: vector[Signature { bytes: signature }], - } -} - -#[test_only] -public(package) fun generate( - weighted_signers: WeightedSigners, - message_to_sign: &vector, - keypairs: &vector, -): Proof { - let signatures = keypairs.map_ref!( - |keypair| new_signature( - ecdsa::secp256k1_sign( - keypair.private_key(), - message_to_sign, - 0, - true, - ), - ), - ); - create_for_testing(weighted_signers, signatures) -} - -// ----- -// Tests -// ----- -#[test] -fun test_getters() { - let proof = dummy(); - - assert!(proof.signers() == proof.signers); - assert!(proof.signatures() == proof.signatures); -} - -#[test] -#[expected_failure(abort_code = EInvalidSignatureLength)] -fun test_new_signature_invalid_signature_length() { - new_signature(vector[]); -} - -#[test] -fun test_recover_pub_key() { - let mut rng = sui::random::new_generator_for_testing(); - let keypair = ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)); - let message = rng.generate_bytes(32); - let signature = new_signature( - ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), - ); - assert!(signature.recover_pub_key(&message) == keypair.public_key()); -} - -#[test] -fun test_validate() { - let mut rng = sui::random::new_generator_for_testing(); - let mut keypairs = vector[0, 1, 2].map!( - |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), - ); - let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); - let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); - let message = rng.generate_bytes(32); - let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); - - let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( - vector[0, 1, 2].map!( - |index| axelar_gateway::weighted_signer::new( - pub_keys[index], - weights[index], - ), - ), - weights[0] + weights[2], - nonce, - ); - keypairs.remove(1); - let proof = generate(weighted_signers, &message, &keypairs); - proof.validate(message); -} - -#[test] -#[expected_failure(abort_code = ERedundantSignaturesProvided)] -fun test_validate_redundant_signatures() { - let mut rng = sui::random::new_generator_for_testing(); - let keypairs = vector[0, 1, 2].map!( - |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), - ); - let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); - let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); - let message = rng.generate_bytes(32); - let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); - - let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( - vector[0, 1, 2].map!( - |index| axelar_gateway::weighted_signer::new( - pub_keys[index], - weights[index], - ), - ), - weights[0] + weights[1], - nonce, - ); - let proof = generate(weighted_signers, &message, &keypairs); - proof.validate(message); -} - -#[test] -#[expected_failure(abort_code = ELowSignaturesWeight)] -fun test_validate_empty_signers() { - let mut rng = sui::random::new_generator_for_testing(); - let message = rng.generate_bytes(32); - let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); - let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( - vector[], - 1, - nonce, - ); - let proof = create_for_testing(weighted_signers, vector[]); - proof.validate(message); -} - -#[test] -#[expected_failure(abort_code = ELowSignaturesWeight)] -fun test_validate_low_signature_weight() { - let mut rng = sui::random::new_generator_for_testing(); - let keypairs = vector[0, 1, 2].map!( - |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), - ); - let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); - let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); - let message = rng.generate_bytes(32); - let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); - - let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( - vector[0, 1, 2].map!( - |index| axelar_gateway::weighted_signer::new( - pub_keys[index], - weights[index], - ), - ), - weights[0] + weights[2] + 1, - nonce, - ); - let signatures = keypairs.map!( - |keypair| new_signature( - ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), - ), - ); - let proof = create_for_testing( - weighted_signers, - vector[signatures[0], signatures[2]], - ); - proof.validate(message); -} - -#[test] -#[expected_failure(abort_code = ESignerNotFound)] -fun test_validate_signer_not_found() { - let mut rng = sui::random::new_generator_for_testing(); - let keypairs = vector[0, 1, 2].map!( - |_| ecdsa::secp256k1_keypair_from_seed(&rng.generate_bytes(32)), - ); - let pub_keys = keypairs.map_ref!(|keypair| *keypair.public_key()); - let weights = vector[0, 1, 2].map!(|_| rng.generate_u64() as u128); - let message = rng.generate_bytes(32); - let nonce = axelar_gateway::bytes32::from_bytes(rng.generate_bytes(32)); - - let weighted_signers = axelar_gateway::weighted_signers::create_for_testing( - vector[0, 2].map!( - |index| axelar_gateway::weighted_signer::new( - pub_keys[index], - weights[index], - ), - ), - weights[0] + weights[2], - nonce, - ); - let signatures = keypairs.map!( - |keypair| new_signature( - ecdsa::secp256k1_sign(keypair.private_key(), &message, 0, true), - ), - ); - let proof = create_for_testing(weighted_signers, vector[signatures[1]]); - proof.validate(message); -} diff --git a/move/axelar_gateway_v1/sources/types/weighted_signer.move b/move/axelar_gateway_v1/sources/types/weighted_signer.move deleted file mode 100644 index 90d4479e..00000000 --- a/move/axelar_gateway_v1/sources/types/weighted_signer.move +++ /dev/null @@ -1,162 +0,0 @@ -module axelar_gateway::weighted_signer; - -use sui::bcs::BCS; - -// --------- -// Constants -// --------- - -/// Length of a public key -const PUB_KEY_LENGTH: u64 = 33; - -// ----- -// Types -// ----- - -public struct WeightedSigner has copy, drop, store { - pub_key: vector, - weight: u128, -} - -public fun pub_key(self: &WeightedSigner): vector { - self.pub_key -} - -public fun weight(self: &WeightedSigner): u128 { - self.weight -} - -// ------ -// Errors -// ------ - -#[error] -const EInvalidPubKeyLength: vector = - b"invalid public key length: expected 33 bytes"; - -#[error] -const EInvalidWeight: vector = b"invalid weight: expected non-zero value"; - -// ----------------- -// Package Functions -// ----------------- - -public(package) fun new(pub_key: vector, weight: u128): WeightedSigner { - assert!(pub_key.length() == PUB_KEY_LENGTH, EInvalidPubKeyLength); - - WeightedSigner { pub_key, weight } -} - -/// Empty weighted signer -public(package) fun default(): WeightedSigner { - let mut pub_key = @0x0.to_bytes(); - pub_key.push_back(0); - - 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(); - - new(pub_key, weight) -} - -public(package) fun validate(self: &WeightedSigner) { - assert!(self.weight != 0, EInvalidWeight); -} - -/// Check if self.signer is less than other.signer as bytes -public(package) fun lt(self: &WeightedSigner, other: &WeightedSigner): bool { - let mut i = 0; - - 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 - }; - - i = i + 1; - }; - - false -} - -// ----- -// Tests -// ----- - -#[test] -#[expected_failure(abort_code = EInvalidPubKeyLength)] -fun test_new_incorrect_pubkey() { - let mut rng = sui::random::new_generator_for_testing(); - new(vector[], rng.generate_u128()); -} - -#[test] -#[expected_failure(abort_code = EInvalidWeight)] -fun test_validate_invalid_weight() { - validate( - &WeightedSigner { - pub_key: vector[], - weight: 0, - }, - ) -} - -#[test] -fun test_pub_key() { - let mut rng = sui::random::new_generator_for_testing(); - let pub_key = rng.generate_bytes(3); - assert!( - &WeightedSigner { - pub_key, - weight: 0, - }.pub_key() == &pub_key, - ); -} - -#[test] -fun verify_default_signer() { - let signer = default(); - - assert!(signer.weight == 0); - assert!(signer.pub_key.length() == PUB_KEY_LENGTH); - - let mut i = 0; - while (i < PUB_KEY_LENGTH) { - assert!(signer.pub_key[i] == 0); - i = i + 1; - } -} - -#[test] -fun compare_weight_signers() { - let signer1 = new( - x"000100000000000000000000000000000000000000000000000000000000000000", - 1, - ); - let signer2 = new( - x"000200000000000000000000000000000000000000000000000000000000000000", - 2, - ); - let signer3 = new( - x"000100000000000000000000000000000000000000000000000000000000000001", - 3, - ); - - // Less than - assert!(signer1.lt(&signer2)); - assert!(signer1.lt(&signer3)); - - // Not less than - assert!(!signer2.lt(&signer1)); - assert!(!signer3.lt(&signer1)); - - // Equal - assert!(!signer1.lt(&signer1)); // !(signer1 < signer1) -} diff --git a/move/axelar_gateway_v1/sources/types/weighted_signers.move b/move/axelar_gateway_v1/sources/types/weighted_signers.move deleted file mode 100644 index 51f6153a..00000000 --- a/move/axelar_gateway_v1/sources/types/weighted_signers.move +++ /dev/null @@ -1,207 +0,0 @@ -module axelar_gateway::weighted_signers; - -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, -} - -/// ------ -/// Errors -/// ------ -#[error] -const EInvalidSignersLength: vector = - b"invalid signers length: expected at least 1 signer"; - -#[error] -const EInvalidThreshold: vector = - b"invalid threshold: expected non-zero value and less than or equal to the total weight of the signers"; - -#[error] -const EInvalidSignerOrder: vector = - b"invalid signer order: signers must be in ascending order by their public key"; - -/// ----------------- -/// Package Functions -/// ----------------- - -/// Decode a `WeightedSigners` from the BCS encoded bytes. -public(package) fun peel(bcs: &mut BCS): WeightedSigners { - let len = bcs.peel_vec_length(); - assert!(len > 0, EInvalidSignersLength); - - WeightedSigners { - signers: vector::tabulate!(len, |_| weighted_signer::peel(bcs)), - threshold: bcs.peel_u128(), - nonce: bytes32::peel(bcs), - } -} - -/// Validates the weighted signers. The following must be true: -/// 1. The signers are in ascending order by their public key. -/// 2. The threshold is greater than zero. -/// 3. The threshold is less than or equal to the total weight of the signers. -public(package) fun validate(self: &WeightedSigners) { - self.validate_signers(); - self.validate_threshold(); -} - -public(package) fun hash(self: &WeightedSigners): Bytes32 { - bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self))) -} - -public(package) fun signers(self: &WeightedSigners): &vector { - &self.signers -} - -public(package) fun threshold(self: &WeightedSigners): u128 { - self.threshold -} - -public(package) fun nonce(self: &WeightedSigners): Bytes32 { - self.nonce -} - -/// ----- -/// Internal Functions -/// ----- - -/// Validates the order of the signers and the length of the signers. -/// The signers must be in ascending order by their public key. -/// Otherwise, the error `EInvalidSignersLength` is raised. -fun validate_signers(self: &WeightedSigners) { - assert!(!self.signers.is_empty(), EInvalidSignersLength); - let mut previous = &weighted_signer::default(); - self.signers.do_ref!(|signer| { - signer.validate(); - assert!(previous.lt(signer), EInvalidSignerOrder); - previous = signer; - }); -} - -/// Calculates the total weight of the signers. -fun total_weight(self: &WeightedSigners): u128 { - self - .signers - .fold!(0, |acc, signer| acc + signer.weight()) -} - -/// Validates the threshold. -/// The threshold must be greater than zero and less than or equal to the total -/// weight of the signers. -/// Otherwise, the error `EInvalidThreshold` is raised. -fun validate_threshold(self: &WeightedSigners) { - assert!( - self.threshold != 0 && self.total_weight() >= self.threshold, - EInvalidThreshold, - ); -} - -#[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 mut rng = sui::random::new_generator_for_testing(); - let pub_key = rng.generate_bytes(33); - let signer = axelar_gateway::weighted_signer::new( - pub_key, - rng.generate_u128(), - ); - let nonce = bytes32::from_bytes(rng.generate_bytes(32)); - let threshold = signer.weight(); - WeightedSigners { - signers: vector[signer], - threshold, - nonce, - } -} - -#[test] -fun tent_nonce() { - let weighted_signers = dummy(); - assert!(weighted_signers.nonce() == weighted_signers.nonce); -} - -#[test] -#[expected_failure(abort_code = EInvalidSignersLength)] -fun test_peel_invalid_signers_length() { - let mut rng = sui::random::new_generator_for_testing(); - let mut bcs = bcs::new( - bcs::to_bytes( - &WeightedSigners { - signers: vector[], - threshold: rng.generate_u128(), - nonce: bytes32::from_bytes(rng.generate_bytes(32)), - }, - ), - ); - peel(&mut bcs); -} - -#[test] -#[expected_failure(abort_code = EInvalidSignersLength)] -fun test_validate_signers_invalid_signers_length() { - let mut rng = sui::random::new_generator_for_testing(); - WeightedSigners { - signers: vector[], - threshold: rng.generate_u128(), - nonce: bytes32::from_bytes(rng.generate_bytes(32)), - }.validate_signers(); -} - -#[test] -#[expected_failure(abort_code = EInvalidSignerOrder)] -fun test_validate_signers_invalid_signer_order() { - let mut rng = sui::random::new_generator_for_testing(); - let mut pub_key = @0x0.to_bytes(); - pub_key.push_back(2); - let signer1 = axelar_gateway::weighted_signer::new(pub_key, 1); - pub_key = @0x0.to_bytes(); - pub_key.push_back(1); - let signer2 = axelar_gateway::weighted_signer::new(pub_key, 1); - WeightedSigners { - signers: vector[signer1, signer2], - threshold: rng.generate_u128(), - nonce: bytes32::from_bytes(rng.generate_bytes(32)), - }.validate_signers(); -} - -#[test] -#[expected_failure(abort_code = EInvalidThreshold)] -fun test_validate_zero_threshold() { - let mut rng = sui::random::new_generator_for_testing(); - WeightedSigners { - signers: vector[], - threshold: 0, - nonce: bytes32::from_bytes(rng.generate_bytes(32)), - }.validate_threshold(); -} - -#[test] -#[expected_failure(abort_code = EInvalidThreshold)] -fun test_validate_threshold_above_weight_sum() { - let mut rng = sui::random::new_generator_for_testing(); - let pub_key = rng.generate_bytes(33); - let signer = axelar_gateway::weighted_signer::new(pub_key, 1); - WeightedSigners { - signers: vector[signer], - threshold: 2, - nonce: bytes32::from_bytes(rng.generate_bytes(32)), - }.validate_threshold(); -} diff --git a/move/axelar_gateway_v1/sources/versioned/gateway_v0.move b/move/axelar_gateway_v1/sources/versioned/gateway_v0.move deleted file mode 100644 index 4bf5d732..00000000 --- a/move/axelar_gateway_v1/sources/versioned/gateway_v0.move +++ /dev/null @@ -1,560 +0,0 @@ -module axelar_gateway::gateway_v0; - -use axelar_gateway::auth::AxelarSigners; -use axelar_gateway::bytes32::{Self, Bytes32}; -use axelar_gateway::channel::{Self, ApprovedMessage}; -use axelar_gateway::events; -use axelar_gateway::message::{Self, Message}; -use axelar_gateway::message_status::{Self, MessageStatus}; -use axelar_gateway::message_ticket::MessageTicket; -use axelar_gateway::proof; -use axelar_gateway::weighted_signers; -use std::ascii::String; -use sui::address; -use sui::clock::Clock; -use sui::hash; -use sui::table::{Self, Table}; -use utils::utils; -use version_control::version_control::VersionControl; - -// ------ -// Errors -// ------ -#[error] -const EMessageNotApproved: vector = - b"trying to `take_approved_message` for a message that is not approved"; - -#[error] -const EZeroMessages: vector = b"no messages found"; - -#[error] -const ENotLatestSigners: vector = b"not latest signers"; - -#[error] -const ENewerMessage: vector = - b"message ticket created from newer versions cannot be sent here"; - -#[error] -const ECannotMigrateTwice: vector = - b"attempting to migrate a even though migration is complete"; - -// ----- -// Types -// ----- -/// An object holding the state of the Axelar bridge. -/// The central piece in managing call approval creation and signature -/// verification. -public struct Gateway_v0 has store { - operator: address, - messages: Table, - signers: AxelarSigners, - version_control: VersionControl, -} - -public enum CommandType { - ApproveMessages, - RotateSigners, -} - -// ----------------- -// Package Functions -// ----------------- -/// Init the module by giving a CreatorCap to the sender to allow a full -/// `setup`. -public(package) fun new( - operator: address, - messages: Table, - signers: AxelarSigners, - version_control: VersionControl, -): Gateway_v0 { - Gateway_v0 { - operator, - messages, - signers, - version_control, - } -} - -public(package) fun version_control(self: &Gateway_v0): &VersionControl { - &self.version_control -} - -public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl) { - assert!(self.version_control.allowed_functions().length() == version_control.allowed_functions().length() - 1, ECannotMigrateTwice ); - self.version_control = version_control; -} - -public(package) fun approve_messages( - self: &mut Gateway_v0, - message_data: vector, - proof_data: vector, -) { - let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); - let messages = peel_messages(message_data); - - let _ = self - .signers - .validate_proof( - data_hash(CommandType::ApproveMessages, message_data), - proof, - ); - - messages.do!(|message| self.approve_message(message)); -} - -public(package) fun rotate_signers( - self: &mut Gateway_v0, - clock: &Clock, - new_signers_data: vector, - proof_data: vector, - ctx: &TxContext, -) { - let weighted_signers = utils::peel!( - new_signers_data, - |bcs| weighted_signers::peel(bcs), - ); - let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); - - let enforce_rotation_delay = ctx.sender() != self.operator; - - let is_latest_signers = self - .signers - .validate_proof( - data_hash(CommandType::RotateSigners, new_signers_data), - proof, - ); - assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); - - // This will fail if signers are duplicated - self - .signers - .rotate_signers(clock, weighted_signers, enforce_rotation_delay); -} - -public(package) fun is_message_approved( - self: &Gateway_v0, - 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[command_id] == message_status::approved(message.hash()) -} - -public(package) fun is_message_executed( - self: &Gateway_v0, - source_chain: String, - message_id: String, -): bool { - let command_id = message::message_to_command_id( - source_chain, - message_id, - ); - - self[command_id] == message_status::executed() -} - -/// 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(package) fun take_approved_message( - self: &mut Gateway_v0, - 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[command_id] == message_status::approved(message.hash()), - EMessageNotApproved, - ); - - let message_status_ref = &mut self[command_id]; - *message_status_ref = message_status::executed(); - - events::message_executed( - message, - ); - - channel::create_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ) -} - -public(package) fun send_message( - _self: &Gateway_v0, - message: MessageTicket, - current_version: u64, -) { - let ( - source_id, - destination_chain, - destination_address, - payload, - version, - ) = message.destroy(); - - assert!(version <= current_version, ENewerMessage); - - events::contract_call( - source_id, - destination_chain, - destination_address, - payload, - address::from_bytes(hash::keccak256(&payload)), - ); -} - -// ----------------- -// Private Functions -// ----------------- - -#[syntax(index)] -fun borrow(self: &Gateway_v0, command_id: Bytes32): &MessageStatus { - table::borrow(&self.messages, command_id) -} - -#[syntax(index)] -fun borrow_mut(self: &mut Gateway_v0, command_id: Bytes32): &mut MessageStatus { - table::borrow_mut(&mut self.messages, command_id) -} - -fun peel_messages(message_data: vector): vector { - utils::peel!(message_data, |bcs| { - let messages = vector::tabulate!( - bcs.peel_vec_length(), - |_| message::peel(bcs), - ); - assert!(messages.length() > 0, EZeroMessages); - messages - }) -} - -fun data_hash(command_type: CommandType, data: vector): Bytes32 { - let mut typed_data = vector::singleton(command_type.as_u8()); - typed_data.append(data); - - bytes32::from_bytes(hash::keccak256(&typed_data)) -} - -fun approve_message(self: &mut Gateway_v0, message: message::Message) { - let command_id = message.command_id(); - - // If the message was already approved, ignore it. - if (self.messages.contains(command_id)) { - return - }; - - self - .messages - .add( - command_id, - message_status::approved(message.hash()), - ); - - events::message_approved( - message, - ); -} - -fun as_u8(self: CommandType): u8 { - match (self) { - CommandType::ApproveMessages => 0, - CommandType::RotateSigners => 1, - } -} - -/// --------- -/// Test Only -/// --------- -#[test_only] -use axelar_gateway::weighted_signers::WeightedSigners; -#[test_only] -use sui::bcs; - -#[test_only] -public(package) fun messages_mut( - self: &mut Gateway_v0, -): &mut Table { - &mut self.messages -} - -#[test_only] -public(package) fun signers_mut(self: &mut Gateway_v0): &mut AxelarSigners { - &mut self.signers -} - -#[test_only] -public(package) fun destroy_for_testing( - self: Gateway_v0, -): (address, Table, AxelarSigners, VersionControl) { - let Gateway_v0 { - operator, - messages, - signers, - version_control, - } = self; - (operator, messages, signers, version_control) -} - -#[test_only] -fun dummy(ctx: &mut TxContext): Gateway_v0 { - new( - @0x0, - sui::table::new(ctx), - axelar_gateway::auth::dummy(ctx), - version_control::version_control::new(vector[]), - ) -} - -#[test_only] -public(package) fun approve_messages_data_hash( - messages: vector, -): Bytes32 { - data_hash(CommandType::ApproveMessages, bcs::to_bytes(&messages)) -} - -#[test_only] -public(package) fun rotate_signers_data_hash( - weighted_signers: WeightedSigners, -): Bytes32 { - data_hash(CommandType::RotateSigners, bcs::to_bytes(&weighted_signers)) -} - -#[test_only] -public(package) fun approve_message_for_testing( - self: &mut Gateway_v0, - message: Message, -) { - self.approve_message(message); -} - -/// ----- -/// Tests -/// ----- -#[test] -#[expected_failure(abort_code = EZeroMessages)] -fun test_peel_messages_no_zero_messages() { - peel_messages(sui::bcs::to_bytes(&vector[])); -} - -#[test] -fun test_approve_message() { - let mut rng = sui::random::new_generator_for_testing(); - 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 = rng.generate_bytes(32); - let payload_hash = axelar_gateway::bytes32::new( - sui::address::from_bytes(hash::keccak256(&payload)), - ); - - let message = message::new( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ); - - let mut data = dummy(ctx); - - data.approve_message(message); - // The second approve message should do nothing. - data.approve_message(message); - - assert!( - data.is_message_approved( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ) == - true, - EMessageNotApproved, - ); - - let approved_message = data.take_approved_message( - source_chain, - message_id, - source_address, - channel.to_address(), - payload, - ); - - channel.consume_approved_message(approved_message); - - assert!( - data.is_message_approved( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ) == - false, - EMessageNotApproved, - ); - - assert!( - data.is_message_executed( - source_chain, - message_id, - ) == - true, - EMessageNotApproved, - ); - - data.messages.remove(message.command_id()); - - sui::test_utils::destroy(data); - 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 = sui::bcs::to_bytes(&vector[message1, message2]); - - let messages = peel_messages(bytes); - - assert!(messages.length() == 2); - assert!(messages[0] == message1); - assert!(messages[1] == message2); -} - -#[test] -#[expected_failure] -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 = sui::bcs::to_bytes(&vector[message1]); - bytes.push_back(0); - - peel_messages(bytes); -} - -#[test] -fun test_command_type_as_u8() { - // Note: These must not be changed to avoid breaking Amplifier integration - assert!(CommandType::ApproveMessages.as_u8() == 0); - assert!(CommandType::RotateSigners.as_u8() == 1); -} - -#[test] -fun test_data_hash() { - let mut rng = sui::random::new_generator_for_testing(); - let data = rng.generate_bytes(32); - let mut typed_data = vector::singleton(CommandType::ApproveMessages.as_u8()); - typed_data.append(data); - - assert!( - data_hash(CommandType::ApproveMessages, data) == - bytes32::from_bytes(hash::keccak256(&typed_data)), - EMessageNotApproved, - ); -} - -#[test] -#[expected_failure(abort_code = ENewerMessage)] -fun test_send_message_newer_message() { - let mut rng = sui::random::new_generator_for_testing(); - let source_id = address::from_u256(rng.generate_u256()); - let destination_chain = std::ascii::string(b"Destination Chain"); - let destination_address = std::ascii::string(b"Destination Address"); - let payload = rng.generate_bytes(32); - let version = 1; - let message = axelar_gateway::message_ticket::new( - source_id, - destination_chain, - destination_address, - payload, - version, - ); - let ctx = &mut sui::tx_context::dummy(); - let self = dummy(ctx); - self.send_message(message, 0); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure(abort_code = EMessageNotApproved)] -fun test_take_approved_message_message_not_approved() { - let mut rng = sui::random::new_generator_for_testing(); - let destination_id = address::from_u256(rng.generate_u256()); - let source_chain = std::ascii::string(b"Source Chain"); - let source_address = std::ascii::string(b"Source Address"); - let message_id = std::ascii::string(b"Message Id"); - let payload = rng.generate_bytes(32); - let command_id = message::message_to_command_id(source_chain, message_id); - - let ctx = &mut sui::tx_context::dummy(); - let mut self = dummy(ctx); - - self - .messages - .add( - command_id, - message_status::executed(), - ); - - let approved_message = self.take_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ); - sui::test_utils::destroy(self); - sui::test_utils::destroy(approved_message); -} From a5e899cab972d895fdf20247e1f5ca5ab6b16e56 Mon Sep 17 00:00:00 2001 From: maancham Date: Fri, 22 Nov 2024 12:21:43 -0500 Subject: [PATCH 15/26] feat: add controlledTest function --- test/axelar-gateway.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/test/axelar-gateway.js b/test/axelar-gateway.js index 5c3e04cb..6531827b 100644 --- a/test/axelar-gateway.js +++ b/test/axelar-gateway.js @@ -15,7 +15,26 @@ const { expect } = require('chai'); const COMMAND_TYPE_ROTATE_SIGNERS = 1; -describe('Axelar Gateway', () => { +const EXPECTED_FAILING_TESTS = (process.env.FAILING_TESTS || '').split(',').filter(Boolean); + +function controlledTest(testName, testFn) { + if (EXPECTED_FAILING_TESTS.includes(testName)) { + it(testName, async () => { + try { + await testFn(); + throw new Error(`Test "${testName}" was expected to fail but passed`); + } catch (error) { + if (error.message === `Test "${testName}" was expected to fail but passed`) { + throw error; + } + } + }); + } else { + it(testName, testFn); + } +} + +describe.only('Axelar Gateway', () => { let client; const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); const deployer = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); @@ -115,7 +134,7 @@ describe('Axelar Gateway', () => { }); describe('Signer Rotation', () => { - it('should rotate signers', async () => { + controlledTest('should rotate signers', async () => { await sleep(2000); const proofSigners = gatewayInfo.signers; const proofKeys = gatewayInfo.signerKeys; @@ -147,7 +166,7 @@ describe('Axelar Gateway', () => { await builder.signAndExecute(keypair); }); - it('Should not rotate to empty signers', async () => { + controlledTest('Should not rotate to empty signers', async () => { await sleep(2000); const proofSigners = gatewayInfo.signers; const proofKeys = gatewayInfo.signerKeys; @@ -206,7 +225,7 @@ describe('Axelar Gateway', () => { channel = response.objectChanges.find((change) => change.objectType === `${packageId}::channel::Channel`).objectId; }); - it('should send a message', async () => { + controlledTest('should send a message', async () => { const destinationChain = 'Destination Chain'; const destinationAddress = 'Destination Address'; const payload = '0x1234'; @@ -236,7 +255,7 @@ describe('Axelar Gateway', () => { }); }); - it('should approve a message', async () => { + controlledTest('should approve a message', async () => { const message = { source_chain: 'Ethereum', message_id: 'Message Id', @@ -270,7 +289,7 @@ describe('Axelar Gateway', () => { expect(bcs.Bool.parse(new Uint8Array(resp.results[2].returnValues[0][0]))).to.equal(false); }); - it('should execute a message', async () => { + controlledTest('should execute a message', async () => { await publishPackage(client, keypair, 'gas_service'); await publishPackage(client, keypair, 'abi'); await publishPackage(client, keypair, 'governance'); From b4cde6669bc473878dd1fd1cdb42116506881e56 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 25 Nov 2024 13:11:47 +0200 Subject: [PATCH 16/26] make gateway upgrade to versioned --- move/axelar_gateway/sources/gateway.move | 55 +- .../sources/versioned/gateway_v0.move | 534 +---------------- .../sources/versioned/gateway_v1.move | 551 ++++++++++++++++++ 3 files changed, 588 insertions(+), 552 deletions(-) create mode 100644 move/axelar_gateway/sources/versioned/gateway_v1.move diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 6f0d7b84..7db62680 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -33,13 +33,14 @@ /// - CallApproval is checked to match the `Channel`.id /// /// The Gateway object uses a versioned field to support upgradability. The -/// current implementation uses Gateway_v0. +/// current implementation uses Gateway_v1. module axelar_gateway::gateway; use axelar_gateway::auth::{Self, validate_proof}; use axelar_gateway::bytes32::{Self, Bytes32}; use axelar_gateway::channel::{Channel, ApprovedMessage}; -use axelar_gateway::gateway_v0::{Self, Gateway_v0}; +use axelar_gateway::gateway_v1::{Self, Gateway_v1}; +use axelar_gateway::gateway_v0::{Gateway_v0}; use axelar_gateway::message_ticket::{Self, MessageTicket}; use axelar_gateway::weighted_signers; use std::ascii::{Self, String}; @@ -99,7 +100,7 @@ entry fun setup( let inner = versioned::create( VERSION, - gateway_v0::new( + gateway_v1::new( operator, table::new(ctx), auth::setup( @@ -129,9 +130,9 @@ entry fun setup( // Macros // ------ /// This macro also uses version control to sinplify things a bit. -macro fun value($self: &Gateway, $function_name: vector): &Gateway_v0 { +macro fun value($self: &Gateway, $function_name: vector): &Gateway_v1 { let gateway = $self; - let value = gateway.inner.load_value(); + let value = gateway.inner.load_value(); value.version_control().check(VERSION, ascii::string($function_name)); value } @@ -140,9 +141,9 @@ macro fun value($self: &Gateway, $function_name: vector): &Gateway_v0 { macro fun value_mut( $self: &mut Gateway, $function_name: vector, -): &mut Gateway_v0 { +): &mut Gateway_v1 { let gateway = $self; - let value = gateway.inner.load_value_mut(); + let value = gateway.inner.load_value_mut(); value.version_control().check(VERSION, ascii::string($function_name)); value } @@ -189,7 +190,13 @@ entry fun rotate_signers( /// (checks should be made on versioned to ensure this) /// It upgrades the version control to the new version control. entry fun migrate(self: &mut Gateway) { - self.inner.load_value_mut().migrate(version_control()); + let (v0, cap) = self.inner.remove_value_for_upgrade(); + let v1 = v0.migrate(version_control()); + self.inner.upgrade( + VERSION, + v1, + cap + ); } // ---------------- @@ -274,13 +281,7 @@ public fun take_approved_message( fun version_control(): VersionControl { version_control::new(vector[ - vector[ - b"approve_messages", - b"rotate_signers", - b"is_message_approved", - b"is_message_executed", - b"take_approved_message", - ].map!(|function_name| function_name.to_ascii_string()), + vector[], vector[ b"approve_messages", b"rotate_signers", @@ -314,7 +315,7 @@ public fun create_for_testing( ): Gateway { let inner = versioned::create( VERSION, - gateway_v0::new( + gateway_v1::new( operator, table::new(ctx), auth::setup( @@ -340,7 +341,7 @@ fun dummy(ctx: &mut TxContext): Gateway { let mut rng = sui::random::new_generator_for_testing(); let inner = versioned::create( VERSION, - gateway_v0::new( + gateway_v1::new( sui::address::from_bytes(rng.generate_bytes(32)), table::new(ctx), auth::dummy(ctx), @@ -373,7 +374,7 @@ public fun destroy_for_testing(self: Gateway) { } = self; id.delete(); - let value = inner.destroy(); + let value = inner.destroy(); let (_, messages, signers, _) = value.destroy_for_testing(); let (_, table, _, _, _, _) = signers.destroy_for_testing(); @@ -440,7 +441,7 @@ fun test_setup() { id.delete(); let (operator_result, messages, signers, _) = inner - .destroy() + .destroy() .destroy_for_testing(); assert!(operator == operator_result); @@ -515,7 +516,7 @@ fun test_setup_remaining_bytes() { id.delete(); let (operator_result, messages, signers, _) = inner - .destroy() + .destroy() .destroy_for_testing(); assert!(operator == operator_result); @@ -671,7 +672,7 @@ fun test_approve_messages() { let messages = vector[ axelar_gateway::message::dummy(), ]; - let data_hash = gateway_v0::approve_messages_data_hash(messages); + let data_hash = gateway_v1::approve_messages_data_hash(messages); let proof = generate_proof( data_hash, domain_separator, @@ -721,7 +722,7 @@ fun test_approve_messages_remaining_data() { ctx, ); let messages = vector[axelar_gateway::message::dummy()]; - let data_hash = gateway_v0::approve_messages_data_hash(messages); + let data_hash = gateway_v1::approve_messages_data_hash(messages); let proof = generate_proof( data_hash, domain_separator, @@ -785,7 +786,7 @@ fun test_rotate_signers() { utils::assert_event(); - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let data_hash = gateway_v1::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( data_hash, domain_separator, @@ -857,7 +858,7 @@ fun test_rotate_signers_remaining_data_message_data() { let mut message_data = bcs::to_bytes(&next_weighted_signers); message_data.push_back(0); - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let data_hash = gateway_v1::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( data_hash, domain_separator, @@ -919,7 +920,7 @@ fun test_rotate_signers_remaining_data_proof_data() { ctx, ); - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let data_hash = gateway_v1::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( data_hash, domain_separator, @@ -942,7 +943,7 @@ fun test_rotate_signers_remaining_data_proof_data() { } #[test] -#[expected_failure(abort_code = axelar_gateway::gateway_v0::ENotLatestSigners)] +#[expected_failure(abort_code = axelar_gateway::gateway_v1::ENotLatestSigners)] fun test_rotate_signers_not_latest_signers() { let mut rng = sui::random::new_generator_for_testing(); let ctx = &mut sui::tx_context::dummy(); @@ -991,7 +992,7 @@ fun test_rotate_signers_not_latest_signers() { let epoch = self.value_mut!(b"rotate_signers").signers_mut().epoch_mut(); *epoch = *epoch + 1; - let data_hash = gateway_v0::rotate_signers_data_hash(next_weighted_signers); + let data_hash = gateway_v1::rotate_signers_data_hash(next_weighted_signers); let proof = generate_proof( data_hash, domain_separator, diff --git a/move/axelar_gateway/sources/versioned/gateway_v0.move b/move/axelar_gateway/sources/versioned/gateway_v0.move index 4bf5d732..88ea1216 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v0.move +++ b/move/axelar_gateway/sources/versioned/gateway_v0.move @@ -1,42 +1,11 @@ module axelar_gateway::gateway_v0; use axelar_gateway::auth::AxelarSigners; -use axelar_gateway::bytes32::{Self, Bytes32}; -use axelar_gateway::channel::{Self, ApprovedMessage}; -use axelar_gateway::events; -use axelar_gateway::message::{Self, Message}; -use axelar_gateway::message_status::{Self, MessageStatus}; -use axelar_gateway::message_ticket::MessageTicket; -use axelar_gateway::proof; -use axelar_gateway::weighted_signers; -use std::ascii::String; -use sui::address; -use sui::clock::Clock; -use sui::hash; -use sui::table::{Self, Table}; -use utils::utils; +use axelar_gateway::bytes32::Bytes32; +use axelar_gateway::message_status::MessageStatus; +use sui::table::Table; use version_control::version_control::VersionControl; - -// ------ -// Errors -// ------ -#[error] -const EMessageNotApproved: vector = - b"trying to `take_approved_message` for a message that is not approved"; - -#[error] -const EZeroMessages: vector = b"no messages found"; - -#[error] -const ENotLatestSigners: vector = b"not latest signers"; - -#[error] -const ENewerMessage: vector = - b"message ticket created from newer versions cannot be sent here"; - -#[error] -const ECannotMigrateTwice: vector = - b"attempting to migrate a even though migration is complete"; +use axelar_gateway::gateway_v1::{Self, Gateway_v1}; // ----- // Types @@ -59,502 +28,17 @@ public enum CommandType { // ----------------- // Package Functions // ----------------- -/// Init the module by giving a CreatorCap to the sender to allow a full -/// `setup`. -public(package) fun new( - operator: address, - messages: Table, - signers: AxelarSigners, - version_control: VersionControl, -): Gateway_v0 { - Gateway_v0 { +public(package) fun migrate(self: Gateway_v0, version_control: VersionControl): Gateway_v1 { + let Gateway_v0 { operator, messages, signers, - version_control, - } -} - -public(package) fun version_control(self: &Gateway_v0): &VersionControl { - &self.version_control -} - -public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl) { - assert!(self.version_control.allowed_functions().length() == version_control.allowed_functions().length() - 1, ECannotMigrateTwice ); - self.version_control = version_control; -} - -public(package) fun approve_messages( - self: &mut Gateway_v0, - message_data: vector, - proof_data: vector, -) { - let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); - let messages = peel_messages(message_data); - - let _ = self - .signers - .validate_proof( - data_hash(CommandType::ApproveMessages, message_data), - proof, - ); - - messages.do!(|message| self.approve_message(message)); -} - -public(package) fun rotate_signers( - self: &mut Gateway_v0, - clock: &Clock, - new_signers_data: vector, - proof_data: vector, - ctx: &TxContext, -) { - let weighted_signers = utils::peel!( - new_signers_data, - |bcs| weighted_signers::peel(bcs), - ); - let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); - - let enforce_rotation_delay = ctx.sender() != self.operator; - - let is_latest_signers = self - .signers - .validate_proof( - data_hash(CommandType::RotateSigners, new_signers_data), - proof, - ); - assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); - - // This will fail if signers are duplicated - self - .signers - .rotate_signers(clock, weighted_signers, enforce_rotation_delay); -} - -public(package) fun is_message_approved( - self: &Gateway_v0, - 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[command_id] == message_status::approved(message.hash()) -} - -public(package) fun is_message_executed( - self: &Gateway_v0, - source_chain: String, - message_id: String, -): bool { - let command_id = message::message_to_command_id( - source_chain, - message_id, - ); - - self[command_id] == message_status::executed() -} - -/// 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(package) fun take_approved_message( - self: &mut Gateway_v0, - 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[command_id] == message_status::approved(message.hash()), - EMessageNotApproved, - ); - - let message_status_ref = &mut self[command_id]; - *message_status_ref = message_status::executed(); - - events::message_executed( - message, - ); - - channel::create_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ) -} - -public(package) fun send_message( - _self: &Gateway_v0, - message: MessageTicket, - current_version: u64, -) { - let ( - source_id, - destination_chain, - destination_address, - payload, - version, - ) = message.destroy(); - - assert!(version <= current_version, ENewerMessage); - - events::contract_call( - source_id, - destination_chain, - destination_address, - payload, - address::from_bytes(hash::keccak256(&payload)), - ); -} - -// ----------------- -// Private Functions -// ----------------- - -#[syntax(index)] -fun borrow(self: &Gateway_v0, command_id: Bytes32): &MessageStatus { - table::borrow(&self.messages, command_id) -} - -#[syntax(index)] -fun borrow_mut(self: &mut Gateway_v0, command_id: Bytes32): &mut MessageStatus { - table::borrow_mut(&mut self.messages, command_id) -} - -fun peel_messages(message_data: vector): vector { - utils::peel!(message_data, |bcs| { - let messages = vector::tabulate!( - bcs.peel_vec_length(), - |_| message::peel(bcs), - ); - assert!(messages.length() > 0, EZeroMessages); - messages - }) -} - -fun data_hash(command_type: CommandType, data: vector): Bytes32 { - let mut typed_data = vector::singleton(command_type.as_u8()); - typed_data.append(data); - - bytes32::from_bytes(hash::keccak256(&typed_data)) -} - -fun approve_message(self: &mut Gateway_v0, message: message::Message) { - let command_id = message.command_id(); - - // If the message was already approved, ignore it. - if (self.messages.contains(command_id)) { - return - }; - - self - .messages - .add( - command_id, - message_status::approved(message.hash()), - ); - - events::message_approved( - message, - ); -} - -fun as_u8(self: CommandType): u8 { - match (self) { - CommandType::ApproveMessages => 0, - CommandType::RotateSigners => 1, - } -} - -/// --------- -/// Test Only -/// --------- -#[test_only] -use axelar_gateway::weighted_signers::WeightedSigners; -#[test_only] -use sui::bcs; - -#[test_only] -public(package) fun messages_mut( - self: &mut Gateway_v0, -): &mut Table { - &mut self.messages -} - -#[test_only] -public(package) fun signers_mut(self: &mut Gateway_v0): &mut AxelarSigners { - &mut self.signers -} - -#[test_only] -public(package) fun destroy_for_testing( - self: Gateway_v0, -): (address, Table, AxelarSigners, VersionControl) { - let Gateway_v0 { + version_control: _, + } = self; + gateway_v1::new( operator, messages, signers, version_control, - } = self; - (operator, messages, signers, version_control) -} - -#[test_only] -fun dummy(ctx: &mut TxContext): Gateway_v0 { - new( - @0x0, - sui::table::new(ctx), - axelar_gateway::auth::dummy(ctx), - version_control::version_control::new(vector[]), ) } - -#[test_only] -public(package) fun approve_messages_data_hash( - messages: vector, -): Bytes32 { - data_hash(CommandType::ApproveMessages, bcs::to_bytes(&messages)) -} - -#[test_only] -public(package) fun rotate_signers_data_hash( - weighted_signers: WeightedSigners, -): Bytes32 { - data_hash(CommandType::RotateSigners, bcs::to_bytes(&weighted_signers)) -} - -#[test_only] -public(package) fun approve_message_for_testing( - self: &mut Gateway_v0, - message: Message, -) { - self.approve_message(message); -} - -/// ----- -/// Tests -/// ----- -#[test] -#[expected_failure(abort_code = EZeroMessages)] -fun test_peel_messages_no_zero_messages() { - peel_messages(sui::bcs::to_bytes(&vector[])); -} - -#[test] -fun test_approve_message() { - let mut rng = sui::random::new_generator_for_testing(); - 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 = rng.generate_bytes(32); - let payload_hash = axelar_gateway::bytes32::new( - sui::address::from_bytes(hash::keccak256(&payload)), - ); - - let message = message::new( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ); - - let mut data = dummy(ctx); - - data.approve_message(message); - // The second approve message should do nothing. - data.approve_message(message); - - assert!( - data.is_message_approved( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ) == - true, - EMessageNotApproved, - ); - - let approved_message = data.take_approved_message( - source_chain, - message_id, - source_address, - channel.to_address(), - payload, - ); - - channel.consume_approved_message(approved_message); - - assert!( - data.is_message_approved( - source_chain, - message_id, - source_address, - channel.to_address(), - payload_hash, - ) == - false, - EMessageNotApproved, - ); - - assert!( - data.is_message_executed( - source_chain, - message_id, - ) == - true, - EMessageNotApproved, - ); - - data.messages.remove(message.command_id()); - - sui::test_utils::destroy(data); - 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 = sui::bcs::to_bytes(&vector[message1, message2]); - - let messages = peel_messages(bytes); - - assert!(messages.length() == 2); - assert!(messages[0] == message1); - assert!(messages[1] == message2); -} - -#[test] -#[expected_failure] -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 = sui::bcs::to_bytes(&vector[message1]); - bytes.push_back(0); - - peel_messages(bytes); -} - -#[test] -fun test_command_type_as_u8() { - // Note: These must not be changed to avoid breaking Amplifier integration - assert!(CommandType::ApproveMessages.as_u8() == 0); - assert!(CommandType::RotateSigners.as_u8() == 1); -} - -#[test] -fun test_data_hash() { - let mut rng = sui::random::new_generator_for_testing(); - let data = rng.generate_bytes(32); - let mut typed_data = vector::singleton(CommandType::ApproveMessages.as_u8()); - typed_data.append(data); - - assert!( - data_hash(CommandType::ApproveMessages, data) == - bytes32::from_bytes(hash::keccak256(&typed_data)), - EMessageNotApproved, - ); -} - -#[test] -#[expected_failure(abort_code = ENewerMessage)] -fun test_send_message_newer_message() { - let mut rng = sui::random::new_generator_for_testing(); - let source_id = address::from_u256(rng.generate_u256()); - let destination_chain = std::ascii::string(b"Destination Chain"); - let destination_address = std::ascii::string(b"Destination Address"); - let payload = rng.generate_bytes(32); - let version = 1; - let message = axelar_gateway::message_ticket::new( - source_id, - destination_chain, - destination_address, - payload, - version, - ); - let ctx = &mut sui::tx_context::dummy(); - let self = dummy(ctx); - self.send_message(message, 0); - sui::test_utils::destroy(self); -} - -#[test] -#[expected_failure(abort_code = EMessageNotApproved)] -fun test_take_approved_message_message_not_approved() { - let mut rng = sui::random::new_generator_for_testing(); - let destination_id = address::from_u256(rng.generate_u256()); - let source_chain = std::ascii::string(b"Source Chain"); - let source_address = std::ascii::string(b"Source Address"); - let message_id = std::ascii::string(b"Message Id"); - let payload = rng.generate_bytes(32); - let command_id = message::message_to_command_id(source_chain, message_id); - - let ctx = &mut sui::tx_context::dummy(); - let mut self = dummy(ctx); - - self - .messages - .add( - command_id, - message_status::executed(), - ); - - let approved_message = self.take_approved_message( - source_chain, - message_id, - source_address, - destination_id, - payload, - ); - sui::test_utils::destroy(self); - sui::test_utils::destroy(approved_message); -} diff --git a/move/axelar_gateway/sources/versioned/gateway_v1.move b/move/axelar_gateway/sources/versioned/gateway_v1.move new file mode 100644 index 00000000..b922ae7d --- /dev/null +++ b/move/axelar_gateway/sources/versioned/gateway_v1.move @@ -0,0 +1,551 @@ +module axelar_gateway::gateway_v1; + +use axelar_gateway::auth::AxelarSigners; +use axelar_gateway::bytes32::{Self, Bytes32}; +use axelar_gateway::channel::{Self, ApprovedMessage}; +use axelar_gateway::events; +use axelar_gateway::message::{Self, Message}; +use axelar_gateway::message_status::{Self, MessageStatus}; +use axelar_gateway::message_ticket::MessageTicket; +use axelar_gateway::proof; +use axelar_gateway::weighted_signers; +use std::ascii::String; +use sui::address; +use sui::clock::Clock; +use sui::hash; +use sui::table::{Self, Table}; +use utils::utils; +use version_control::version_control::VersionControl; + +// ------ +// Errors +// ------ +#[error] +const EMessageNotApproved: vector = + b"trying to `take_approved_message` for a message that is not approved"; + +#[error] +const EZeroMessages: vector = b"no messages found"; + +#[error] +const ENotLatestSigners: vector = b"not latest signers"; + +#[error] +const ENewerMessage: vector = + b"message ticket created from newer versions cannot be sent here"; + +// ----- +// Types +// ----- +/// An object holding the state of the Axelar bridge. +/// The central piece in managing call approval creation and signature +/// verification. +public struct Gateway_v1 has store { + operator: address, + messages: Table, + signers: AxelarSigners, + version_control: VersionControl, +} + +public enum CommandType { + ApproveMessages, + RotateSigners, +} + +// ----------------- +// Package Functions +// ----------------- +/// Init the module by giving a CreatorCap to the sender to allow a full +/// `setup`. +public(package) fun new( + operator: address, + messages: Table, + signers: AxelarSigners, + version_control: VersionControl, +): Gateway_v1 { + Gateway_v1 { + operator, + messages, + signers, + version_control, + } +} + +public(package) fun version_control(self: &Gateway_v1): &VersionControl { + &self.version_control +} + +public(package) fun approve_messages( + self: &mut Gateway_v1, + message_data: vector, + proof_data: vector, +) { + let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); + let messages = peel_messages(message_data); + + let _ = self + .signers + .validate_proof( + data_hash(CommandType::ApproveMessages, message_data), + proof, + ); + + messages.do!(|message| self.approve_message(message)); +} + +public(package) fun rotate_signers( + self: &mut Gateway_v1, + clock: &Clock, + new_signers_data: vector, + proof_data: vector, + ctx: &TxContext, +) { + let weighted_signers = utils::peel!( + new_signers_data, + |bcs| weighted_signers::peel(bcs), + ); + let proof = utils::peel!(proof_data, |bcs| proof::peel(bcs)); + + let enforce_rotation_delay = ctx.sender() != self.operator; + + let is_latest_signers = self + .signers + .validate_proof( + data_hash(CommandType::RotateSigners, new_signers_data), + proof, + ); + assert!(!enforce_rotation_delay || is_latest_signers, ENotLatestSigners); + + // This will fail if signers are duplicated + self + .signers + .rotate_signers(clock, weighted_signers, enforce_rotation_delay); +} + +public(package) fun is_message_approved( + self: &Gateway_v1, + 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[command_id] == message_status::approved(message.hash()) +} + +public(package) fun is_message_executed( + self: &Gateway_v1, + source_chain: String, + message_id: String, +): bool { + let command_id = message::message_to_command_id( + source_chain, + message_id, + ); + + self[command_id] == message_status::executed() +} + +/// 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(package) fun take_approved_message( + self: &mut Gateway_v1, + 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[command_id] == message_status::approved(message.hash()), + EMessageNotApproved, + ); + + let message_status_ref = &mut self[command_id]; + *message_status_ref = message_status::executed(); + + events::message_executed( + message, + ); + + channel::create_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ) +} + +public(package) fun send_message( + _self: &Gateway_v1, + message: MessageTicket, + current_version: u64, +) { + let ( + source_id, + destination_chain, + destination_address, + payload, + version, + ) = message.destroy(); + + assert!(version <= current_version, ENewerMessage); + + events::contract_call( + source_id, + destination_chain, + destination_address, + payload, + address::from_bytes(hash::keccak256(&payload)), + ); +} + +// ----------------- +// Private Functions +// ----------------- + +#[syntax(index)] +fun borrow(self: &Gateway_v1, command_id: Bytes32): &MessageStatus { + table::borrow(&self.messages, command_id) +} + +#[syntax(index)] +fun borrow_mut(self: &mut Gateway_v1, command_id: Bytes32): &mut MessageStatus { + table::borrow_mut(&mut self.messages, command_id) +} + +fun peel_messages(message_data: vector): vector { + utils::peel!(message_data, |bcs| { + let messages = vector::tabulate!( + bcs.peel_vec_length(), + |_| message::peel(bcs), + ); + assert!(messages.length() > 0, EZeroMessages); + messages + }) +} + +fun data_hash(command_type: CommandType, data: vector): Bytes32 { + let mut typed_data = vector::singleton(command_type.as_u8()); + typed_data.append(data); + + bytes32::from_bytes(hash::keccak256(&typed_data)) +} + +fun approve_message(self: &mut Gateway_v1, message: message::Message) { + let command_id = message.command_id(); + + // If the message was already approved, ignore it. + if (self.messages.contains(command_id)) { + return + }; + + self + .messages + .add( + command_id, + message_status::approved(message.hash()), + ); + + events::message_approved( + message, + ); +} + +fun as_u8(self: CommandType): u8 { + match (self) { + CommandType::ApproveMessages => 0, + CommandType::RotateSigners => 1, + } +} + +/// --------- +/// Test Only +/// --------- +#[test_only] +use axelar_gateway::weighted_signers::WeightedSigners; +#[test_only] +use sui::bcs; + +#[test_only] +public(package) fun messages_mut( + self: &mut Gateway_v1, +): &mut Table { + &mut self.messages +} + +#[test_only] +public(package) fun signers_mut(self: &mut Gateway_v1): &mut AxelarSigners { + &mut self.signers +} + +#[test_only] +public(package) fun destroy_for_testing( + self: Gateway_v1, +): (address, Table, AxelarSigners, VersionControl) { + let Gateway_v1 { + operator, + messages, + signers, + version_control, + } = self; + (operator, messages, signers, version_control) +} + +#[test_only] +fun dummy(ctx: &mut TxContext): Gateway_v1 { + new( + @0x0, + sui::table::new(ctx), + axelar_gateway::auth::dummy(ctx), + version_control::version_control::new(vector[]), + ) +} + +#[test_only] +public(package) fun approve_messages_data_hash( + messages: vector, +): Bytes32 { + data_hash(CommandType::ApproveMessages, bcs::to_bytes(&messages)) +} + +#[test_only] +public(package) fun rotate_signers_data_hash( + weighted_signers: WeightedSigners, +): Bytes32 { + data_hash(CommandType::RotateSigners, bcs::to_bytes(&weighted_signers)) +} + +#[test_only] +public(package) fun approve_message_for_testing( + self: &mut Gateway_v1, + message: Message, +) { + self.approve_message(message); +} + +/// ----- +/// Tests +/// ----- +#[test] +#[expected_failure(abort_code = EZeroMessages)] +fun test_peel_messages_no_zero_messages() { + peel_messages(sui::bcs::to_bytes(&vector[])); +} + +#[test] +fun test_approve_message() { + let mut rng = sui::random::new_generator_for_testing(); + 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 = rng.generate_bytes(32); + let payload_hash = axelar_gateway::bytes32::new( + sui::address::from_bytes(hash::keccak256(&payload)), + ); + + let message = message::new( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ); + + let mut data = dummy(ctx); + + data.approve_message(message); + // The second approve message should do nothing. + data.approve_message(message); + + assert!( + data.is_message_approved( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ) == + true, + EMessageNotApproved, + ); + + let approved_message = data.take_approved_message( + source_chain, + message_id, + source_address, + channel.to_address(), + payload, + ); + + channel.consume_approved_message(approved_message); + + assert!( + data.is_message_approved( + source_chain, + message_id, + source_address, + channel.to_address(), + payload_hash, + ) == + false, + EMessageNotApproved, + ); + + assert!( + data.is_message_executed( + source_chain, + message_id, + ) == + true, + EMessageNotApproved, + ); + + data.messages.remove(message.command_id()); + + sui::test_utils::destroy(data); + 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 = sui::bcs::to_bytes(&vector[message1, message2]); + + let messages = peel_messages(bytes); + + assert!(messages.length() == 2); + assert!(messages[0] == message1); + assert!(messages[1] == message2); +} + +#[test] +#[expected_failure] +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 = sui::bcs::to_bytes(&vector[message1]); + bytes.push_back(0); + + peel_messages(bytes); +} + +#[test] +fun test_command_type_as_u8() { + // Note: These must not be changed to avoid breaking Amplifier integration + assert!(CommandType::ApproveMessages.as_u8() == 0); + assert!(CommandType::RotateSigners.as_u8() == 1); +} + +#[test] +fun test_data_hash() { + let mut rng = sui::random::new_generator_for_testing(); + let data = rng.generate_bytes(32); + let mut typed_data = vector::singleton(CommandType::ApproveMessages.as_u8()); + typed_data.append(data); + + assert!( + data_hash(CommandType::ApproveMessages, data) == + bytes32::from_bytes(hash::keccak256(&typed_data)), + EMessageNotApproved, + ); +} + +#[test] +#[expected_failure(abort_code = ENewerMessage)] +fun test_send_message_newer_message() { + let mut rng = sui::random::new_generator_for_testing(); + let source_id = address::from_u256(rng.generate_u256()); + let destination_chain = std::ascii::string(b"Destination Chain"); + let destination_address = std::ascii::string(b"Destination Address"); + let payload = rng.generate_bytes(32); + let version = 1; + let message = axelar_gateway::message_ticket::new( + source_id, + destination_chain, + destination_address, + payload, + version, + ); + let ctx = &mut sui::tx_context::dummy(); + let self = dummy(ctx); + self.send_message(message, 0); + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EMessageNotApproved)] +fun test_take_approved_message_message_not_approved() { + let mut rng = sui::random::new_generator_for_testing(); + let destination_id = address::from_u256(rng.generate_u256()); + let source_chain = std::ascii::string(b"Source Chain"); + let source_address = std::ascii::string(b"Source Address"); + let message_id = std::ascii::string(b"Message Id"); + let payload = rng.generate_bytes(32); + let command_id = message::message_to_command_id(source_chain, message_id); + + let ctx = &mut sui::tx_context::dummy(); + let mut self = dummy(ctx); + + self + .messages + .add( + command_id, + message_status::executed(), + ); + + let approved_message = self.take_approved_message( + source_chain, + message_id, + source_address, + destination_id, + payload, + ); + sui::test_utils::destroy(self); + sui::test_utils::destroy(approved_message); +} From c54515c403570edb366aefc214eb409dae8301eb Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 26 Nov 2024 14:50:51 +0100 Subject: [PATCH 17/26] try adding more files --- UPGRADE.md | 3 +++ package.json | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 UPGRADE.md diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..3069a579 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,3 @@ +## Expected post-Upgrade Behaviour + +Everything except `send_message` should work on the original package, and everything should work on the newer package. diff --git a/package.json b/package.json index 9d98ac6b..cbaef241 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,12 @@ "move", "version.json", "tsconfig.json", - "!move/**/build" + "!move/**/build", + "src", + "scripts", + "tsconfig.*.json", + "tsconfig.json", + "UPGRADE.md" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", From 5777fafac269ffa290de6097ccb3d0cf21d1eee6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 26 Nov 2024 14:57:35 +0100 Subject: [PATCH 18/26] add postinstall --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cbaef241..3cae8688 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "verify-web-build": "node scripts/verify-web-build.js", "lint": "eslint --fix './src/**/*.ts' './test/*.js'", "prettier": "prettier --write './src/**/*.ts' './test/*.js'", - "docs": "./scripts/docs.sh" + "docs": "./scripts/docs.sh", + "postinstall": "npm run build" }, "keywords": [ "axelar", From 66cf7339b9a04a57e1e8205d8cc4f4c928bced15 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 26 Nov 2024 15:04:25 +0100 Subject: [PATCH 19/26] add files --- package.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9d98ac6b..3cae8688 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,12 @@ "move", "version.json", "tsconfig.json", - "!move/**/build" + "!move/**/build", + "src", + "scripts", + "tsconfig.*.json", + "tsconfig.json", + "UPGRADE.md" ], "main": "dist/cjs/index.js", "module": "dist/esm/index.js", @@ -52,7 +57,8 @@ "verify-web-build": "node scripts/verify-web-build.js", "lint": "eslint --fix './src/**/*.ts' './test/*.js'", "prettier": "prettier --write './src/**/*.ts' './test/*.js'", - "docs": "./scripts/docs.sh" + "docs": "./scripts/docs.sh", + "postinstall": "npm run build" }, "keywords": [ "axelar", From fde9d16792ebb90cb0abe79325f6a87160efd9f6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 12 Dec 2024 17:35:21 +0200 Subject: [PATCH 20/26] add allow and disallow functions back and fix tests --- move/axelar_gateway/sources/gateway.move | 4 ++-- .../sources/versioned/gateway_v1.move | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 5f1ade98..cbb5d72b 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -1091,7 +1091,7 @@ fun test_allow_function() { let ctx = &mut sui::tx_context::dummy(); let mut self = dummy(ctx); let owner_cap = owner_cap::create(ctx); - let version = 0; + let version = VERSION; let function_name = b"function_name".to_ascii_string(); self.allow_function(&owner_cap, version, function_name); @@ -1105,7 +1105,7 @@ fun test_disallow_function() { let ctx = &mut sui::tx_context::dummy(); let mut self = dummy(ctx); let owner_cap = owner_cap::create(ctx); - let version = 0; + let version = VERSION; let function_name = b"approve_messages".to_ascii_string(); self.disallow_function(&owner_cap, version, function_name); diff --git a/move/axelar_gateway/sources/versioned/gateway_v1.move b/move/axelar_gateway/sources/versioned/gateway_v1.move index b922ae7d..ab18dc30 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v1.move +++ b/move/axelar_gateway/sources/versioned/gateway_v1.move @@ -221,6 +221,22 @@ public(package) fun send_message( ); } +public(package) fun allow_function( + self: &mut Gateway_v1, + version: u64, + function_name: String, +) { + self.version_control.allow_function(version, function_name); +} + +public(package) fun disallow_function( + self: &mut Gateway_v1, + version: u64, + function_name: String, +) { + self.version_control.disallow_function(version, function_name); +} + // ----------------- // Private Functions // ----------------- From 80196e6e97716632547ba2e15345ebab145e37a6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 12 Dec 2024 18:56:44 +0200 Subject: [PATCH 21/26] update upgrade.md --- UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 3069a579..eceb8771 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,3 @@ ## Expected post-Upgrade Behaviour -Everything except `send_message` should work on the original package, and everything should work on the newer package. +Nothing should work on the original package, and everything should work on the newer package. From f588fa3e07d619cff7cf18428b2ee9fe65beccac Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 13 Dec 2024 18:34:20 +0200 Subject: [PATCH 22/26] Update the version to 2 and added a new field --- move/axelar_gateway/sources/gateway.move | 4 +++- move/axelar_gateway/sources/versioned/gateway_v1.move | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index cbb5d72b..49e39aed 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -54,7 +54,7 @@ use version_control::version_control::{Self, VersionControl}; // ------- // Version // ------- -const VERSION: u64 = 1; +const VERSION: u64 = 2; // ------- // Structs @@ -290,6 +290,7 @@ public fun take_approved_message( fun version_control(): VersionControl { version_control::new(vector[ + vector[], vector[], vector[ b"approve_messages", @@ -357,6 +358,7 @@ fun dummy(ctx: &mut TxContext): Gateway { table::new(ctx), auth::dummy(ctx), version_control::new(vector[ + vector[], vector[], vector[ b"approve_messages", diff --git a/move/axelar_gateway/sources/versioned/gateway_v1.move b/move/axelar_gateway/sources/versioned/gateway_v1.move index ab18dc30..6554db0a 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v1.move +++ b/move/axelar_gateway/sources/versioned/gateway_v1.move @@ -45,6 +45,7 @@ public struct Gateway_v1 has store { messages: Table, signers: AxelarSigners, version_control: VersionControl, + new_field: u64, } public enum CommandType { @@ -68,6 +69,7 @@ public(package) fun new( messages, signers, version_control, + new_field: 0, } } @@ -325,6 +327,7 @@ public(package) fun destroy_for_testing( messages, signers, version_control, + new_field: _, } = self; (operator, messages, signers, version_control) } From 393a494487cd3f2922a674419837c355336f8ed2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 13 Dec 2024 19:26:22 +0200 Subject: [PATCH 23/26] add some changes for upgradability ot work with main --- move/axelar_gateway/Move.toml | 4 ++-- move/axelar_gateway/sources/gateway.move | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/move/axelar_gateway/Move.toml b/move/axelar_gateway/Move.toml index 73002bec..217a73ef 100644 --- a/move/axelar_gateway/Move.toml +++ b/move/axelar_gateway/Move.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet-v1.38.2" } -MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "testnet-v1.38.2" } +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet-v1.38.3" } +MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "mainnet-v1.38.3" } VersionControl = { local = "../version_control" } Utils = { local = "../utils" } diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 701988ce..92326d86 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -289,6 +289,8 @@ fun version_control(): VersionControl { b"is_message_approved", b"is_message_executed", b"take_approved_message", + b"allow_function", + b"disallow_function", ].map!(|function_name| function_name.to_ascii_string()), vector[ b"approve_messages", From e23e6cf443aac54ac932b29139f02a64b0108123 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 16 Dec 2024 13:51:45 +0200 Subject: [PATCH 24/26] upgrade migrate signature --- move/axelar_gateway/sources/gateway.move | 4 ++-- move/axelar_gateway/sources/versioned/gateway_v0.move | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 92326d86..36cd31fe 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -177,8 +177,8 @@ entry fun rotate_signers( /// This function should only be called once /// (checks should be made on versioned to ensure this) /// It upgrades the version control to the new version control. -entry fun migrate(self: &mut Gateway) { - self.inner.load_value_mut().migrate(version_control()); +entry fun migrate(self: &mut Gateway, _: &OwnerCap, data: vector) { + self.inner.load_value_mut().migrate(version_control(), data); } entry fun allow_function( diff --git a/move/axelar_gateway/sources/versioned/gateway_v0.move b/move/axelar_gateway/sources/versioned/gateway_v0.move index cea2ff7e..054b344e 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v0.move +++ b/move/axelar_gateway/sources/versioned/gateway_v0.move @@ -38,6 +38,10 @@ const ENewerMessage: vector = const ECannotMigrateTwice: vector = b"attempting to migrate a even though migration is complete"; +#[error] +const ENoDataAllowedOnMigrate: vector = + b"no data should be passed to migrate to this version"; + // ----- // Types // ----- @@ -79,8 +83,9 @@ public(package) fun version_control(self: &Gateway_v0): &VersionControl { &self.version_control } -public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl) { +public(package) fun migrate(self: &mut Gateway_v0, mut version_control: VersionControl, data: vector) { assert!(self.version_control.allowed_functions().length() == version_control.allowed_functions().length() - 1, ECannotMigrateTwice ); + assert!(data.length() == 0, ENoDataAllowedOnMigrate); self.version_control = version_control; } From 8aa28c121130e0bd99b3a1d441b9f9f6ca9ddffd Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 16 Dec 2024 14:05:24 +0200 Subject: [PATCH 25/26] added accessors for the new field --- move/axelar_gateway/sources/gateway.move | 11 +++++++++++ .../axelar_gateway/sources/versioned/gateway_v0.move | 8 +++----- .../axelar_gateway/sources/versioned/gateway_v1.move | 12 +++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index cb10f985..948407c9 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -104,6 +104,7 @@ entry fun setup( ctx, ), version_control(), + 0, ), ctx, ); @@ -208,6 +209,14 @@ entry fun disallow_function( .disallow_function(version, function_name); } +entry fun set_new_field(self: &mut Gateway, new_field: u64) { + self.value_mut!(b"set_new_field").set_new_field(new_field); +} + +entry fun new_field(self: &Gateway): u64 { + self.value!(b"new_field").new_field() +} + // ---------------- // Public Functions // ---------------- @@ -339,6 +348,7 @@ public fun create_for_testing( ctx, ), version_control(), + 0, ), ctx, ); @@ -372,6 +382,7 @@ fun dummy(ctx: &mut TxContext): Gateway { b"", ].map!(|function_name| function_name.to_ascii_string()), ]), + 0, ), ctx, ); diff --git a/move/axelar_gateway/sources/versioned/gateway_v0.move b/move/axelar_gateway/sources/versioned/gateway_v0.move index 93aaf4de..a60f9a2a 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v0.move +++ b/move/axelar_gateway/sources/versioned/gateway_v0.move @@ -6,10 +6,7 @@ use axelar_gateway::message_status::MessageStatus; use sui::table::Table; use version_control::version_control::VersionControl; use axelar_gateway::gateway_v1::{Self, Gateway_v1}; - -#[error] -const ENoDataAllowedOnMigrate: vector = - b"no data should be passed to migrate to this version"; +use utils::utils; // ----- // Types @@ -39,11 +36,12 @@ public(package) fun migrate(self: Gateway_v0, version_control: VersionControl, d signers, version_control: _, } = self; + let new_field = utils::peel!(data, |bcs| bcs.peel_u64()); gateway_v1::new( operator, messages, signers, version_control, - data, + new_field, ) } diff --git a/move/axelar_gateway/sources/versioned/gateway_v1.move b/move/axelar_gateway/sources/versioned/gateway_v1.move index 6554db0a..abb87f53 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v1.move +++ b/move/axelar_gateway/sources/versioned/gateway_v1.move @@ -63,13 +63,14 @@ public(package) fun new( messages: Table, signers: AxelarSigners, version_control: VersionControl, + new_field: u64, ): Gateway_v1 { Gateway_v1 { operator, messages, signers, version_control, - new_field: 0, + new_field, } } @@ -239,6 +240,14 @@ public(package) fun disallow_function( self.version_control.disallow_function(version, function_name); } +public(package) fun set_new_field(self: &mut Gateway_v1, new_field: u64) { + self.new_field = new_field; +} + +public(package) fun new_field(self: &Gateway_v1): u64 { + self.new_field +} + // ----------------- // Private Functions // ----------------- @@ -339,6 +348,7 @@ fun dummy(ctx: &mut TxContext): Gateway_v1 { sui::table::new(ctx), axelar_gateway::auth::dummy(ctx), version_control::version_control::new(vector[]), + 0, ) } From 1e572d282a0cb49f2b5f707903d385a15726eef9 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 19 Dec 2024 15:54:17 +0200 Subject: [PATCH 26/26] allow set_new_field and new_field functions by default --- move/axelar_gateway/sources/gateway.move | 2 ++ 1 file changed, 2 insertions(+) diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index 948407c9..a731fe4c 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -310,6 +310,8 @@ fun version_control(): VersionControl { b"send_message", b"allow_function", b"disallow_function", + b"set_new_field", + b"new_field", ].map!(|function_name| function_name.to_ascii_string()), ]) }