From 67fb0b53de8d5c3fe11367b68bd171dabd59e2d1 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 30 Oct 2024 15:47:57 +0200 Subject: [PATCH 01/13] removed remote decimals from all move files --- move/its/sources/events.move | 8 ++--- move/its/sources/its.move | 28 +++------------ move/its/sources/types/coin_data.move | 4 --- move/its/sources/types/coin_info.move | 24 ++----------- move/its/sources/types/coin_management.move | 31 +++++----------- move/its/sources/types/token_id.move | 5 --- move/its/sources/versioned/its_v0.move | 39 ++++++--------------- 7 files changed, 28 insertions(+), 111 deletions(-) diff --git a/move/its/sources/events.move b/move/its/sources/events.move index 9a348669..8bbb7231 100644 --- a/move/its/sources/events.move +++ b/move/its/sources/events.move @@ -20,7 +20,7 @@ public struct InterchainTransfer has copy, drop { source_address: address, destination_chain: String, destination_address: vector, - amount: u256, + amount: u64, data_hash: Bytes32, } @@ -38,7 +38,7 @@ public struct InterchainTransferReceived has copy, drop { source_chain: String, source_address: vector, destination_address: address, - amount: u256, + amount: u64, data_hash: Bytes32, } @@ -62,7 +62,7 @@ public(package) fun interchain_transfer( source_address: address, destination_chain: String, destination_address: vector, - amount: u256, + amount: u64, data: &vector, ) { let data_hash = bytes32::new(address::from_bytes(keccak256(data))); @@ -98,7 +98,7 @@ public(package) fun interchain_transfer_received( source_chain: String, source_address: vector, destination_address: address, - amount: u256, + amount: u64, data: &vector, ) { let data_hash = bytes32::new(address::from_bytes(keccak256(data))); diff --git a/move/its/sources/its.move b/move/its/sources/its.move index 30122ae5..3e76e718 100644 --- a/move/its/sources/its.move +++ b/move/its/sources/its.move @@ -336,10 +336,6 @@ const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u256 = 1; #[test_only] const ITS_HUB_ROUTING_IDENTIFIER: vector = b"hub"; -// === The maximum number of decimals allowed === -#[test_only] -const DECIMALS_CAP: u8 = 9; - #[test_only] public fun create_for_testing(ctx: &mut TxContext): ITS { let mut version_control = version_control(); @@ -416,7 +412,6 @@ fun test_register_coin() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let coin_management = its::coin_management::new_locked(); @@ -432,13 +427,11 @@ fun test_deploy_remote_interchain_token() { let token_name = string::utf8(b"Name"); let token_symbol = ascii::string(b"Symbol"); let token_decimals = 10; - let remote_decimals = 12; let coin_info = its::coin_info::from_info( token_name, token_symbol, token_decimals, - remote_decimals, ); let coin_management = its::coin_management::new_locked(); @@ -483,9 +476,7 @@ fun test_deploy_interchain_token() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); - let scaling = coin_info.scaling(); let coin_management = its::coin_management::new_locked(); let token_id = register_coin(&mut its, coin_info, coin_management); @@ -517,7 +508,7 @@ fun test_deploy_interchain_token() { .write_u256(token_id.to_u256()) .write_bytes(source_channel.to_address().to_bytes()) .write_bytes(destination_address) - .write_u256((amount as u256) * scaling) + .write_u256((amount as u256)) .write_bytes(b""); assert!( @@ -546,7 +537,6 @@ fun test_receive_interchain_transfer() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -595,9 +585,7 @@ fun test_receive_interchain_transfer_with_data() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); - let scaling = coin_info.scaling(); let amount = 1234; let data = b"some_data"; @@ -646,7 +634,7 @@ fun test_receive_interchain_transfer_with_data() { assert!(received_source_chain == source_chain); assert!(received_source_address == its_source_address); assert!(received_data == data); - assert!(received_coin.value() == amount / (scaling as u64)); + assert!(received_coin.value() == amount); clock.destroy_for_testing(); channel.destroy(); @@ -665,9 +653,7 @@ fun test_receive_deploy_interchain_token() { let source_address = ascii::string(b"Address"); let name = b"Token Name"; let symbol = b"Symbol"; - let remote_decimals = 12; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; + let decimals = 9; let token_id: u256 = 1234; its.value_mut!(b"").create_unregistered_coin(symbol, decimals, ctx); @@ -678,7 +664,7 @@ fun test_receive_deploy_interchain_token() { .write_u256(token_id) .write_bytes(name) .write_bytes(symbol) - .write_u256((remote_decimals as u256)) + .write_u256((decimals as u256)) .write_bytes(vector::empty()); let payload = writer.into_bytes(); @@ -720,7 +706,6 @@ fun test_mint_as_distributor() { let mut its = create_for_testing(ctx); let symbol = b"COIN"; let decimals = 9; - let remote_decimals = 18; let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( symbol, @@ -729,7 +714,6 @@ fun test_mint_as_distributor() { ); let coin_info = its::coin_info::from_metadata( coin_metadata, - remote_decimals, ); let mut coin_management = its::coin_management::new_with_cap(treasury_cap); @@ -759,7 +743,6 @@ fun test_mint_to_as_distributor() { let mut its = create_for_testing(ctx); let symbol = b"COIN"; let decimals = 9; - let remote_decimals = 18; let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( symbol, @@ -768,7 +751,6 @@ fun test_mint_to_as_distributor() { ); let coin_info = its::coin_info::from_metadata( coin_metadata, - remote_decimals, ); let mut coin_management = its::coin_management::new_with_cap(treasury_cap); @@ -796,7 +778,6 @@ fun test_burn_as_distributor() { let mut its = create_for_testing(ctx); let symbol = b"COIN"; let decimals = 9; - let remote_decimals = 18; let amount = 1234; let ( @@ -806,7 +787,6 @@ fun test_burn_as_distributor() { let coin = treasury_cap.mint(amount, ctx); let coin_info = its::coin_info::from_metadata( coin_metadata, - remote_decimals, ); let mut coin_management = its::coin_management::new_with_cap(treasury_cap); diff --git a/move/its/sources/types/coin_data.move b/move/its/sources/types/coin_data.move index 1bdbc506..7c310230 100644 --- a/move/its/sources/types/coin_data.move +++ b/move/its/sources/types/coin_data.move @@ -33,7 +33,3 @@ public(package) fun coin_management_mut( ): &mut CoinManagement { &mut self.coin_management } - -public(package) fun coin_scaling(self: &CoinData): u256 { - self.coin_info.scaling() -} diff --git a/move/its/sources/types/coin_info.move b/move/its/sources/types/coin_info.move index cb56b814..9795dd01 100644 --- a/move/its/sources/types/coin_info.move +++ b/move/its/sources/types/coin_info.move @@ -2,7 +2,6 @@ /// either derived from `CoinMetadata` or manually provided. module its::coin_info; -use its::utils; use std::ascii; use std::string::String; use sui::coin::CoinMetadata; @@ -11,7 +10,6 @@ public struct CoinInfo has store { name: String, symbol: ascii::String, decimals: u8, - remote_decimals: u8, metadata: Option>, } @@ -20,27 +18,21 @@ public fun from_info( name: String, symbol: ascii::String, decimals: u8, - remote_decimals: u8, ): CoinInfo { CoinInfo { name, symbol, decimals, - remote_decimals, metadata: option::none(), } } /// Create a new coin info from the given `CoinMetadata` object. -public fun from_metadata( - metadata: CoinMetadata, - remote_decimals: u8, -): CoinInfo { +public fun from_metadata(metadata: CoinMetadata): CoinInfo { CoinInfo { name: metadata.get_name(), symbol: metadata.get_symbol(), decimals: metadata.get_decimals(), - remote_decimals, metadata: option::some(metadata), } } @@ -59,14 +51,6 @@ public fun decimals(self: &CoinInfo): u8 { self.decimals } -public fun remote_decimals(self: &CoinInfo): u8 { - self.remote_decimals -} - -public fun scaling(self: &CoinInfo): u256 { - utils::pow(10, self.remote_decimals - self.decimals) -} - public fun metadata(self: &CoinInfo): &Option> { &self.metadata } @@ -82,7 +66,6 @@ public fun drop(coin_info: CoinInfo) { name: _, symbol: _, decimals: _, - remote_decimals: _, metadata, } = coin_info; if (metadata.is_some()) { @@ -101,18 +84,15 @@ fun test_from_metadata() { let name = metadata.get_name(); let symbol = metadata.get_symbol(); let decimals = metadata.get_decimals(); - let remote_decimals = 31; - let coin_info = from_metadata(metadata, remote_decimals); + let coin_info = from_metadata(metadata); assert!(coin_info.name() == name); assert!(coin_info.symbol() == symbol); assert!(coin_info.decimals() == decimals); - assert!(coin_info.remote_decimals() == remote_decimals); assert!( sui::bcs::to_bytes(coin_info.metadata().borrow()) == metadata_bytes, ); - assert!(coin_info.scaling() == utils::pow(10, remote_decimals - decimals)); sui::test_utils::destroy(coin_info); } diff --git a/move/its/sources/types/coin_management.move b/move/its/sources/types/coin_management.move index dff1a19c..b6197a17 100644 --- a/move/its/sources/types/coin_management.move +++ b/move/its/sources/types/coin_management.move @@ -22,7 +22,6 @@ public struct CoinManagement has store { distributor: Option
, operator: Option
, flow_limit: FlowLimit, - scaling: u256, dust: u256, } @@ -38,7 +37,6 @@ public fun new_with_cap(treasury_cap: TreasuryCap): CoinManagement { distributor: option::none(), operator: option::none(), flow_limit: flow_limit::new(), - scaling: 0, // placeholder, this gets edited when a coin is registered. dust: 0, } } @@ -52,7 +50,6 @@ public fun new_locked(): CoinManagement { distributor: option::none(), operator: option::none(), flow_limit: flow_limit::new(), - scaling: 0, // placeholder, this gets edited when a coin is registered. dust: 0, } } @@ -97,9 +94,9 @@ public(package) fun take_balance( self: &mut CoinManagement, to_take: Balance, clock: &Clock, -): u256 { +): u64 { self.flow_limit.add_flow_out(to_take.value(), clock); - let amount = (to_take.value() as u256) * self.scaling; + let amount = to_take.value(); if (has_capability(self)) { self.burn(to_take); } else { @@ -112,28 +109,18 @@ public(package) fun take_balance( /// previous transfers is added to the coin here. public(package) fun give_coin( self: &mut CoinManagement, - mut amount: u256, + amount: u64, clock: &Clock, ctx: &mut TxContext, ): Coin { - amount = amount + self.dust; - self.dust = amount % self.scaling; - let sui_amount = (amount / self.scaling as u64); - self.flow_limit.add_flow_in(sui_amount, clock); + self.flow_limit.add_flow_in(amount, clock); if (has_capability(self)) { - self.mint(sui_amount, ctx) + self.mint(amount, ctx) } else { - coin::take(self.balance.borrow_mut(), sui_amount, ctx) + coin::take(self.balance.borrow_mut(), amount, ctx) } } -public(package) fun set_scaling( - self: &mut CoinManagement, - scaling: u256, -) { - self.scaling = scaling; -} - // helper function to mint as a distributor. public(package) fun mint( self: &mut CoinManagement, @@ -214,10 +201,9 @@ fun test_give_coin() { let mut coin = cap.mint(amount1, ctx); let mut management1 = new_locked(); - management1.scaling = 1; let clock = sui::clock::create_for_testing(ctx); management1.take_balance(coin.into_balance(), &clock); - coin = management1.give_coin((amount1 as u256), &clock, ctx); + coin = management1.give_coin(amount1, &clock, ctx); assert!(management1.balance.borrow().value() == 0); assert!(coin.value() == amount1); @@ -225,8 +211,7 @@ fun test_give_coin() { sui::test_utils::destroy(coin); let mut management2 = new_with_cap(cap); - management2.scaling = 1; - coin = management2.give_coin((amount2 as u256), &clock, ctx); + coin = management2.give_coin(amount2, &clock, ctx); assert!(coin.value() == amount2); diff --git a/move/its/sources/types/token_id.move b/move/its/sources/types/token_id.move index b0542858..a2ebddd4 100644 --- a/move/its/sources/types/token_id.move +++ b/move/its/sources/types/token_id.move @@ -37,7 +37,6 @@ public fun from_info( name: &String, symbol: &ascii::String, decimals: &u8, - remote_decimals: &u8, has_metadata: &bool, has_treasury: &bool, ): TokenId { @@ -46,7 +45,6 @@ public fun from_info( vec.append(bcs::to_bytes(name)); vec.append(bcs::to_bytes(symbol)); vec.append(bcs::to_bytes(decimals)); - vec.append(bcs::to_bytes(remote_decimals)); vec.append(bcs::to_bytes(has_metadata)); vec.append(bcs::to_bytes(has_treasury)); TokenId { id: address::from_bytes(keccak256(&vec)) } @@ -60,7 +58,6 @@ public(package) fun from_coin_data( &coin_info.name(), &coin_info.symbol(), &coin_info.decimals(), - &coin_info.remote_decimals(), &option::is_some(coin_info.metadata()), &coin_management.has_capability(), ) @@ -92,12 +89,10 @@ fun test() { let name = string::utf8(b"Name"); let symbol = ascii::string(b"Symbol"); let decimals: u8 = 9; - let remote_decimals: u8 = 18; let coin_info = coin_info::from_info( name, symbol, decimals, - remote_decimals, ); let mut vec = address::from_u256(PREFIX_SUI_TOKEN_ID).to_bytes(); diff --git a/move/its/sources/versioned/its_v0.move b/move/its/sources/versioned/its_v0.move index 20f021da..d94e6b79 100644 --- a/move/its/sources/versioned/its_v0.move +++ b/move/its/sources/versioned/its_v0.move @@ -77,9 +77,6 @@ const ITS_HUB_CHAIN_NAME: vector = b"axelar"; // For Sui this will probably be every supported chain. const ITS_HUB_ROUTING_IDENTIFIER: vector = b"hub"; -// === The maximum number of decimals allowed === -const DECIMALS_CAP: u8 = 9; - // ----- // Types // ----- @@ -286,7 +283,7 @@ public(package) fun send_interchain_transfer( .write_u256(token_id.to_u256()) .write_bytes(source_address.to_bytes()) .write_bytes(destination_address) - .write_u256(amount) + .write_u256((amount as u256)) .write_bytes(data); events::interchain_transfer( @@ -319,7 +316,7 @@ public(package) fun receive_interchain_transfer( let token_id = token_id::from_u256(reader.read_u256()); let source_address = reader.read_bytes(); let destination_address = address::from_bytes(reader.read_bytes()); - let amount = reader.read_u256(); + let amount = (reader.read_u256() as u64); let data = reader.read_bytes(); assert!(data.is_empty(), EInterchainTransferHasData); @@ -361,7 +358,7 @@ public(package) fun receive_interchain_transfer_with_data( let source_address = reader.read_bytes(); let destination_address = address::from_bytes(reader.read_bytes()); - let amount = reader.read_u256(); + let amount = (reader.read_u256() as u64); let data = reader.read_bytes(); assert!(destination_address == channel.to_address(), EWrongDestination); @@ -396,10 +393,8 @@ public(package) fun receive_deploy_interchain_token( let token_id = token_id::from_u256(reader.read_u256()); let name = string::utf8(reader.read_bytes()); let symbol = ascii::string(reader.read_bytes()); - let remote_decimals = (reader.read_u256() as u8); + let decimals = (reader.read_u256() as u8); let distributor_bytes = reader.read_bytes(); - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; let (treasury_cap, mut coin_metadata) = self.remove_unregistered_coin( token_id::unregistered_token_id(&symbol, decimals), ); @@ -407,7 +402,7 @@ public(package) fun receive_deploy_interchain_token( treasury_cap.update_name(&mut coin_metadata, name); let mut coin_management = coin_management::new_with_cap(treasury_cap); - let coin_info = coin_info::from_metadata(coin_metadata, remote_decimals); + let coin_info = coin_info::from_metadata(coin_metadata); if (distributor_bytes.length() > 0) { let distributor = address::from_bytes(distributor_bytes); @@ -589,10 +584,9 @@ fun add_registered_coin_type( fun add_registered_coin( self: &mut ITS_v0, token_id: TokenId, - mut coin_management: CoinManagement, + coin_management: CoinManagement, coin_info: CoinInfo, ) { - coin_management.set_scaling(coin_info.scaling()); self .registered_coins .add( @@ -903,7 +897,6 @@ fun test_receive_interchain_transfer_invalid_message_type() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -953,7 +946,6 @@ fun test_receive_interchain_transfer_passed_data() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -1002,7 +994,6 @@ fun test_receive_interchain_transfer_with_data_invalid_message_type() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -1062,7 +1053,6 @@ fun test_receive_interchain_transfer_with_data_wrong_destination() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -1122,7 +1112,6 @@ fun test_receive_interchain_transfer_with_data_no_data() { string::utf8(b"Name"), ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; @@ -1182,9 +1171,7 @@ fun test_receive_deploy_interchain_token_with_distributor() { let source_address = ascii::string(b"Address"); let name = b"Token Name"; let symbol = b"Symbol"; - let remote_decimals = 8; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; + let decimals = 9; let token_id: u256 = 1234; let distributor = @0x1; @@ -1196,7 +1183,7 @@ fun test_receive_deploy_interchain_token_with_distributor() { .write_u256(token_id) .write_bytes(name) .write_bytes(symbol) - .write_u256((remote_decimals as u256)) + .write_u256((decimals as u256)) .write_bytes(distributor.to_bytes()); let payload = writer.into_bytes(); @@ -1226,9 +1213,7 @@ fun test_receive_deploy_interchain_token_invalid_message_type() { let source_address = ascii::string(b"Address"); let name = b"Token Name"; let symbol = b"Symbol"; - let remote_decimals = 8; - let decimals = if (remote_decimals > DECIMALS_CAP) DECIMALS_CAP - else remote_decimals; + let decimals = 9; let token_id: u256 = 1234; self.create_unregistered_coin(symbol, decimals, ctx); @@ -1239,7 +1224,7 @@ fun test_receive_deploy_interchain_token_invalid_message_type() { .write_u256(token_id) .write_bytes(name) .write_bytes(symbol) - .write_u256((remote_decimals as u256)) + .write_u256((decimals as u256)) .write_bytes(b""); let payload = writer.into_bytes(); @@ -1290,7 +1275,6 @@ fun test_burn_as_distributor_not_distributor() { let mut self = create_for_testing(ctx); let symbol = b"COIN"; let decimals = 9; - let remote_decimals = 18; let amount = 1234; let ( @@ -1300,7 +1284,6 @@ fun test_burn_as_distributor_not_distributor() { let coin = treasury_cap.mint(amount, ctx); let coin_info = its::coin_info::from_metadata( coin_metadata, - remote_decimals, ); let mut coin_management = its::coin_management::new_with_cap(treasury_cap); @@ -1360,7 +1343,6 @@ fun test_mint_as_distributor_not_distributor() { let mut self = create_for_testing(ctx); let symbol = b"COIN"; let decimals = 9; - let remote_decimals = 18; let (treasury_cap, coin_metadata) = its::coin::create_treasury_and_metadata( symbol, @@ -1369,7 +1351,6 @@ fun test_mint_as_distributor_not_distributor() { ); let coin_info = its::coin_info::from_metadata( coin_metadata, - remote_decimals, ); let mut coin_management = its::coin_management::new_with_cap(treasury_cap); From 4cc38f44b0011ac52200a009d4e124ce878e9061 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 30 Oct 2024 16:08:05 +0200 Subject: [PATCH 02/13] fixed tests --- move/example/sources/its/its.move | 1 - move/squid/sources/squid/squid.move | 1 - .../sources/squid/versioned/squid_v0.move | 1 - test/testdata/interface_its_coin_info.json | 22 ++----------------- test/testdata/interface_its_token_id.json | 1 - 5 files changed, 2 insertions(+), 24 deletions(-) diff --git a/move/example/sources/its/its.move b/move/example/sources/its/its.move index e88e30d0..48f0b94a 100644 --- a/move/example/sources/its/its.move +++ b/move/example/sources/its/its.move @@ -148,7 +148,6 @@ public fun register_coin( coin_metadata.get_name(), coin_metadata.get_symbol(), coin_metadata.get_decimals(), - coin_metadata.get_decimals(), ); let coin_management = coin_management::new_locked(); diff --git a/move/squid/sources/squid/squid.move b/move/squid/sources/squid/squid.move index 6eda45ff..94dcbe3b 100644 --- a/move/squid/sources/squid/squid.move +++ b/move/squid/sources/squid/squid.move @@ -138,7 +138,6 @@ fun test_start_swap() { std::string::utf8(b"Name"), std::ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; diff --git a/move/squid/sources/squid/versioned/squid_v0.move b/move/squid/sources/squid/versioned/squid_v0.move index 936dbdce..f234c683 100644 --- a/move/squid/sources/squid/versioned/squid_v0.move +++ b/move/squid/sources/squid/versioned/squid_v0.move @@ -86,7 +86,6 @@ fun test_start_swap() { std::string::utf8(b"Name"), std::ascii::string(b"Symbol"), 10, - 12, ); let amount = 1234; diff --git a/test/testdata/interface_its_coin_info.json b/test/testdata/interface_its_coin_info.json index 2478d0b9..4a25348c 100644 --- a/test/testdata/interface_its_coin_info.json +++ b/test/testdata/interface_its_coin_info.json @@ -7,8 +7,7 @@ "params": { "name#0#0": "String", "symbol#0#0": "String", - "decimals#0#0": "u8", - "remote_decimals#0#0": "u8" + "decimals#0#0": "u8" }, "returnType": "CoinInfo" }, @@ -16,8 +15,7 @@ "name": "from_metadata", "visibility": "public", "params": { - "metadata#0#0": "CoinMetadata", - "remote_decimals#0#0": "u8" + "metadata#0#0": "CoinMetadata" }, "returnType": "CoinInfo" }, @@ -45,22 +43,6 @@ }, "returnType": "u8" }, - "remote_decimals": { - "name": "remote_decimals", - "visibility": "public", - "params": { - "self#0#0": "&CoinInfo" - }, - "returnType": "u8" - }, - "scaling": { - "name": "scaling", - "visibility": "public", - "params": { - "self#0#0": "&CoinInfo" - }, - "returnType": "u256" - }, "metadata": { "name": "metadata", "visibility": "public", diff --git a/test/testdata/interface_its_token_id.json b/test/testdata/interface_its_token_id.json index 00559662..4c92bbc9 100644 --- a/test/testdata/interface_its_token_id.json +++ b/test/testdata/interface_its_token_id.json @@ -61,7 +61,6 @@ "name#0#0": "&String", "symbol#0#0": "&String", "decimals#0#0": "&u8", - "remote_decimals#0#0": "&u8", "has_metadata#0#0": "&bool", "has_treasury#0#0": "&bool" }, From 2f0b7908e4f3d2d45ae48009dce4c178b68822c5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 30 Oct 2024 16:34:34 +0200 Subject: [PATCH 03/13] cs --- .changeset/eight-candles-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eight-candles-rest.md diff --git a/.changeset/eight-candles-rest.md b/.changeset/eight-candles-rest.md new file mode 100644 index 00000000..38488071 --- /dev/null +++ b/.changeset/eight-candles-rest.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': minor +--- + +Removed remote decimals tracking from ITS as it will be handled at the hub. From 7d361c2210752409a53b7c54ae62bf3b2f870f5c Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 31 Oct 2024 17:54:54 +0200 Subject: [PATCH 04/13] test workflow --- .github/workflows/limits.yaml | 40 +++++++ INTEGRATION.md | 68 ++++++++++++ scripts/limits.js | 198 ++++++++++++++++++++++++++++++++++ test/testutils.js | 26 +---- 4 files changed, 309 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/limits.yaml create mode 100644 INTEGRATION.md create mode 100644 scripts/limits.js diff --git a/.github/workflows/limits.yaml b/.github/workflows/limits.yaml new file mode 100644 index 00000000..7792d9b0 --- /dev/null +++ b/.github/workflows/limits.yaml @@ -0,0 +1,40 @@ +name: Find Limits + +on: pull_request + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup tools + uses: ./.github/actions/setup-tools + with: + install-sui: 'true' + install-nodejs: 'true' + + - name: Create limits + run: node scripts/limits.js >> comment.md + + - name: Find Code Coverage comment + uses: peter-evans/find-comment@v3 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Code Coverage Summary + + - name: Create or update Code Coverage comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: ./comment.md + edit-mode: replace diff --git a/INTEGRATION.md b/INTEGRATION.md new file mode 100644 index 00000000..102195f3 --- /dev/null +++ b/INTEGRATION.md @@ -0,0 +1,68 @@ +# Sui Axelar Gateway and ITS Integration + +Sui is a smart contract chain that offers some unique design parameters. For this reason there have been a few differences between the implementation of smart contracts on Sui compared to EVM chains, that are used as reference + +## General Limitations + +### Design Limitation + +There are quite a few differences between EVM and Sui. + +In Sui, there are packages and modules that can run code, similar to EVM smart contracts. A big difference however is that Sui packages/modules do not have any storage available. Storage as a concept only exists on objects, which can be defined and controlled by modules. To access and modify any storage the corresponding object needs to be available. This means that conventional EVM smart contracts effectively combine the functionality of modules and objects in Sui. + +Additionally there is a lack of interfaces whatsoever. It is impossible for a module to ever call another module that is published at a later time. This means applications that want to interface with future applications must be called by those future applications, but need to only call pre-existing ones. To expand on this, we expect contract calls that are received to potentially modify the storage of multiple objects in a single call, which makes it impossible to require modules to implement a 'standardized' function that a relayer will call, because the number of arguments required varies depending on the application (or the type of call). + +Package "upgrades" result in a new, upgraded package being created, while preserving the old package. This new package can modify objects created by the old package, but any external modules that are using the old package will continued to do so, until they are upgraded too. This means that maintaining packages that depend on multiple existing packages is quite difficult if the dependancies are expected to upgrade. + +Finally, we do not want to require the payload of incoming calls to have a certain format, because that would mean that working applications that want to exapnd to Sui need to redesign their working protocoll to accomodate Sui, discouraging them from doing so. + +### Basic Limitations + +See [this doc](https://move-book.com/guides/building-against-limits.html) for reference. The gas limit for sui is 50 SUI, which is over 100 times larger than any transaction needed for this context. There is also a transaction size limit of 128KB. Objects cannot be more than 256KB in size and there cannot be more than 1024 dynamic fields created on an object on a single transaction. See below for how these limitations affect potential designs. + +## Axelar Gateway + +The Axelar gateway is the remote interface for the Axelar Network to interract with any given chain. The reference implementation for such a smart contract can be found [here](https://github.com/axelarnetwork/axelar-cgp-solidity), with some design requirements found specifically [here](https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/contracts/gateway/INTEGRATION.md) + +The Sui message sending looks like this + +```mermaid +flowchart LR + User(User) + SG[Source Gateway] + MT[(MessageTicket)] + + User -->|prepare_message| SG --> MT --> |send_message| SG + SG -.->|ContractCall event| SG + SG -->|Confirm event| AXL +``` + +Receiving calls looks like this (see below for relayer discovery) + +```mermaid +flowchart LR + DG[Destination Gateway] + RD[Relayer Discovery] + AXL{Axelar Amplifier} + Contract[Destination contract] + Transaction[(Transaction)] + + AXL -->|approve_messages| DG + Relayer -->|consume_approved_message| DG --> |ApprovedMessage| Transaction + Relayer -->|get_transaction| RD --> Transaction --> Contract +``` + +The verifier/signer rotation flow looks like the following: + +```mermaid +flowchart LR + AXL{Axelar Amplifier} + DG[Destination Gateway] + + AXL -.->|Verifier set change| AXL + AXL -->|rotate_signers| DG + DG -.->|SignersRotated event| DG + DG --->|Confirm event| AXL +``` +## Relayer Discovery + diff --git a/scripts/limits.js b/scripts/limits.js new file mode 100644 index 00000000..086c58c4 --- /dev/null +++ b/scripts/limits.js @@ -0,0 +1,198 @@ +const { SuiClient, getFullnodeUrl } = require('@mysten/sui/client'); +const { Ed25519Keypair } = require('@mysten/sui/keypairs/ed25519'); +const { Secp256k1Keypair } = require('@mysten/sui/keypairs/secp256k1'); +const { requestSuiFromFaucetV0, getFaucetHost } = require('@mysten/sui/faucet'); +const { publishPackage, getRandomBytes32, approveMessage, hashMessage, signMessage } = require('../test/testutils'); +const { TxBuilder, CLOCK_PACKAGE_ID } = require('../dist/cjs'); +const { + bcsStructs: { + gateway: { WeightedSigners, MessageToSign, Proof }, + }, +} = require('../dist/cjs/bcs'); +const { arrayify, hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); + +const COMMAND_TYPE_ROTATE_SIGNERS = 1; + +let client; +const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); +const deployer = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); +const keypair = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); +const domainSeparator = getRandomBytes32(); +const network = process.env.NETWORK || 'localnet'; +let nonce = 0; +let packageId; +let gateway; +let discovery; +let gatewayInfo = {}; +const discoveryInfo = {}; + +function calculateNextSigners(n = 3) { + signerKeys = Array.from({length: n}, getRandomBytes32); + const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); + const keys = signerKeys.map((key, index) => { + return { privKey: key, pubKey: pubKeys[index] }; + }); + keys.sort((key1, key2) => { + for (let i = 0; i < 33; i++) { + if (key1.pubKey[i] < key2.pubKey[i]) return -1; + if (key1.pubKey[i] > key2.pubKey[i]) return 1; + } + + return 0; + }); + return { + signerKeys: keys.map((key) => key.privKey), + signers: { + signers: keys.map((key) => { + return { pub_key: key.pubKey, weight: 1 }; + }), + threshold: 2, + nonce: hexlify([++nonce]), + }, + } +} + +// Perform a binary search to find the maximum possible balue of n before testFn(n) fails. +async function binarySearch(testFn, min = 1, max = 10000) { + if(max === min + 1) return min + while(max - min > 1) { + const mid = Math.floor((min + max)/2); + try { + await testFn(mid); + min = mid; + } catch(e) { + max = mid; + } + } + return min; +} + +async function sleep(ms = 1000) { + await new Promise((resolve) => setTimeout(resolve, ms)); +} + +const minimumRotationDelay = 1000; +const previousSignersRetention = 15; + +async function getMaxSigners() { + await sleep(2000); + const proofSigners = gatewayInfo.signers; + const proofKeys = gatewayInfo.signerKeys; + return await binarySearch(async (n) => { + let nextSigners = calculateNextSigners(n); + + const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); + + const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); + + const message = MessageToSign.serialize({ + domain_separator: domainSeparator, + signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), + data_hash: hashed, + }).toBytes(); + + const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); + const encodedProof = Proof.serialize({ + signers: proofSigners, + signatures, + }).toBytes(); + + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::rotate_signers`, + arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], + }); + + await builder.signAndExecute(keypair); + gatewayInfo = { + ...nextSigners, + ...gatewayInfo, + }; + }); +} + +async function getMaxApprovals() { + const message = { + source_chain: 'Ethereum', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; + + return await binarySearch(async (n) => { + const messages = Array.from({length: n}, (_, index) => { + return { + message_id: getRandomBytes32(), + ...message, + } + }); + await approveMessage(client, keypair, gatewayInfo, messages); + }); +} + +async function prepare() { + client = new SuiClient({ url: getFullnodeUrl(network) }); + + await Promise.all( + [operator, deployer, keypair].map((keypair) => + requestSuiFromFaucetV0({ + host: getFaucetHost(network), + recipient: keypair.toSuiAddress(), + }), + ), + ); + + await publishPackage(client, deployer, 'utils'); + await publishPackage(client, deployer, 'version_control'); + let result = await publishPackage(client, deployer, 'axelar_gateway'); + packageId = result.packageId; + const creatorCap = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${packageId}::gateway::CreatorCap`, + ).objectId; + result = await publishPackage(client, deployer, 'relayer_discovery'); + const discoveryPackageId = result.packageId; + discovery = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${discoveryPackageId}::discovery::RelayerDiscovery`, + ).objectId; + + gatewayInfo = { + ...calculateNextSigners(), + ... gatewayInfo, + }; + + const encodedSigners = WeightedSigners.serialize(gatewayInfo.signers).toBytes(); + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::setup`, + arguments: [ + creatorCap, + operator.toSuiAddress(), + domainSeparator, + minimumRotationDelay, + previousSignersRetention, + encodedSigners, + CLOCK_PACKAGE_ID, + ], + }); + + result = await builder.signAndExecute(deployer); + + gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`).objectId; + + gatewayInfo.gateway = gateway; + gatewayInfo.domainSeparator = domainSeparator; + gatewayInfo.packageId = packageId; + discoveryInfo.packageId = discoveryPackageId; + discoveryInfo.discovery = discovery; +} + +(async() => { + await prepare(); + console.log(await getMaxApprovals()); + console.log(await getMaxSigners()); +})(); + + + diff --git a/test/testutils.js b/test/testutils.js index 31d62023..4e1dfe0c 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -205,7 +205,8 @@ function calculateNextSigners(gatewayInfo, nonce) { async function approveMessage(client, keypair, gatewayInfo, contractCallInfo) { const { packageId, gateway, signers, signerKeys, domainSeparator } = gatewayInfo; - const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); + if (!Array.isArray(contractCallInfo)) contractCallInfo = [contractCallInfo]; + const messageData = bcs.vector(Message).serialize(contractCallInfo).toBytes(); const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); @@ -234,33 +235,12 @@ async function approveMessage(client, keypair, gatewayInfo, contractCallInfo) { signatures, }).toBytes(); - let builder = new TxBuilder(client); - + const builder = new TxBuilder(client); await builder.moveCall({ target: `${packageId}::gateway::approve_messages`, arguments: [gateway, messageData, encodedProof], }); - await builder.signAndExecute(keypair); - - builder = new TxBuilder(client); - - const payloadHash = await builder.moveCall({ - target: `${packageId}::bytes32::new`, - arguments: [contractCallInfo.payload_hash], - }); - - await builder.moveCall({ - target: `${packageId}::gateway::is_message_approved`, - arguments: [ - gateway, - contractCallInfo.source_chain, - contractCallInfo.message_id, - contractCallInfo.source_address, - contractCallInfo.destination_id, - payloadHash, - ], - }); } async function approveAndExecuteMessage(client, keypair, gatewayInfo, messageInfo, executeOptions) { From bdb9565a06e64a43926215e5273d57bf98404bb2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 31 Oct 2024 17:57:14 +0200 Subject: [PATCH 05/13] add sui netxork to limits --- .github/workflows/limits.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/limits.yaml b/.github/workflows/limits.yaml index 7792d9b0..bf05e61a 100644 --- a/.github/workflows/limits.yaml +++ b/.github/workflows/limits.yaml @@ -19,6 +19,19 @@ jobs: with: install-sui: 'true' install-nodejs: 'true' + + - name: Build TS and Move Modules + run: npm run build + + - name: Initialize Sui Config + run: echo -e "y\n\n1" | sui client envs + + - name: Run Localnet + run: nohup sh -c "RUST_LOG="off,sui_node=info" sui start --with-faucet --force-regenesis" > nohup.out 2> nohup.err < /dev/null & + + - name: Sleep for 30 seconds + run: sleep 30s + shell: bash - name: Create limits run: node scripts/limits.js >> comment.md From 21127c69562959237ab685e097b2f3e32adbd85e Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 1 Nov 2024 16:57:10 +0200 Subject: [PATCH 06/13] format limits --- scripts/limits.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/limits.js b/scripts/limits.js index 086c58c4..2ded7fd2 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -190,8 +190,13 @@ async function prepare() { (async() => { await prepare(); - console.log(await getMaxApprovals()); - console.log(await getMaxSigners()); + const maxApprovals = await getMaxApprovals(); + const maxSigenrs = await getMaxSigners(); + console.log("
"); + console.log(" Click to see the limis") + console.log(`Maximum possible approvals in a call: ${maxApprovals}`); + console.log(`Maximum possible signers in a signer set: ${maxSigenrs}`); + console.log("
"); })(); From 9b37dfd70e51414d6935e8873ebfda2259fab58f Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 1 Nov 2024 17:16:06 +0200 Subject: [PATCH 07/13] update format --- .github/workflows/limits.yaml | 2 +- scripts/limits.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/limits.yaml b/.github/workflows/limits.yaml index bf05e61a..9680da6c 100644 --- a/.github/workflows/limits.yaml +++ b/.github/workflows/limits.yaml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - coverage: + limits: runs-on: ubuntu-latest steps: diff --git a/scripts/limits.js b/scripts/limits.js index 2ded7fd2..e9feba2c 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -194,7 +194,7 @@ async function prepare() { const maxSigenrs = await getMaxSigners(); console.log("
"); console.log(" Click to see the limis") - console.log(`Maximum possible approvals in a call: ${maxApprovals}`); + console.log(`Maximum possible approvals in a call: ${maxApprovals}\n`); console.log(`Maximum possible signers in a signer set: ${maxSigenrs}`); console.log("
"); })(); From 5d4ab933e9a89a5227945d9a87bd4f1531d8e7ac Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 5 Nov 2024 14:21:33 +0200 Subject: [PATCH 08/13] add some more integration comments --- INTEGRATION.md | 163 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/INTEGRATION.md b/INTEGRATION.md index 102195f3..b244553d 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -66,3 +66,166 @@ flowchart LR ``` ## Relayer Discovery +The only information about an incoming contract call through axelar are + +- Command Id: A unique identifier for the command approving the call, 32 bytes long. +- Source Chain: The name of the source chain, as a String +- Source Address: The caller, as a String. +- Destination Chain: “Sui” for the purposes of this document +- Destination Address: The destination, a Sui address. + +As we already explained, the destination address will be the `ID` of a `GatewayChannel` object that needs to be provided to consume the call. However there is no way for a relayer to know what they are supposed to call to get the call to be consumed, since they should not have access to the `GatewayChannel` object itself. This creates the need to make the destination for each incoming call publicly known so that any relayer can query it. + +### Transaction Object + +The following objects are defined in the `axelar::discovery` module and can be used to register/decode transactions. + +```rust +struct Function has store, copy, drop { + package_id: address, + module_name: String, + name: String, +} + +/// Arguments are prefixed with: +/// - 0 for objects followed by exactly 32 bytes that cointain the object id +/// - 1 for pures followed by the bcs encoded form of the pure object +/// - 2 for the ApprovedCall object, followed by nothing (to be passed into the target function) +/// - 3 for the payload of the contract call (to be passed into the intermediate function) +/// - 4 for an argument returned from a previous move call, followed by a u8 specified which call to get the return of (0 for the first transaction AFTER the one that gets ApprovedCall out), and then another u8 specifying which argument to input. +struct MoveCall has store, copy, drop { + function: Function, + arguments: vector>, + type_arguments: vector, +} + +public struct Transaction has store, copy, drop { + is_final: bool, + move_calls: vector, +} +``` + +As seen above there are some options to allow for dynamic objects to be referenced that could not be known on chain, since they do not exist on chain (the `ApprovedCall` object, the payload of the incoming call and the returned arguments of a previous `MoveCall`). + +### Relayer + +First the relayer needs to get a contract call approved: + +```mermaid +graph + process([axelar::gateway::process_commands]) + +subgraph tx1[Get Approval] + subgraph pure1[pure] + input[/commands input: vector<u8>/] + end + subgraph public0[public] + gateway{Gateway} + end + input & gateway --> process +end +``` + +Next, they need to figure out what to actually call to execute: + +```mermaid +graph + getTransaction([axelar::discovery::get_transaction]) + + subgraph public1[pubic] + discovery{RelayerDiscovery} + end + transaction[/Transaction/] + channelId[/Channel Id/] + discovery & channelId --> getTransaction --> devInspect[Dev Inspect] --> transaction + + transaction --> |is_final == true| block[Ready to Execute] + transaction --> |is_final == false| devInspect[Dev Inspect] + +``` + +Finally, they need to get the `ApprovedCall` object from the gateway and use it to execute: + +```mermaid +graph + subgraph public + Gateway{Gateway} + end + + subgraph pure + commandId[/command_id: vector<u8>/] + sourceChain[/source_chain: String/] + sourceAddress[/source_address: String/] + destinationAddress[/destination_channel_id: address/] + payload[/payload: vector<u8>/] + end + subgraph hotpotato[Hot Potato] + ApprovedCall((ApprovedCall)) + end + takeCall([axelar::gateway::takeContractCall]) + Gateway & commandId & sourceChain & sourceAddress & destinationAddress & payload --> takeCall --> ApprovedCall + ApprovedCall --> execute[Execute As Described by `Transaction` Above] + +``` + +### Application + +Anyone can register a `Transaction` for their channel using `axelar::discovery::register_transaction`: + +```mermaid +graph + register([axelar::discovery::register_transaction]) + newFunction([axelar::discovery::new_function]) + newMoveCall([axelar::discovery::new_move_call]) + newTransaction([axelar::discovery::new_transaction]) + + + function[/Function/] + moveCall[/MoveCall/] + transaction[/Transaction/] + + subgraph pure1[pure] + packageId[/package id: address/] + moduleName[/module name: String/] + functionName[/function name: String/] + end + subgraph pure2[pure] + arguments[/arguments: vector<vector<u8>>/] + types[/type aguments: vector<String>/] + end + subgraph pure3[pure] + isFinal[is_final: bool] + end + + subgraph public + discovery{RelayerDiscovery} + end + + subgraph user + channel{{GatewayChannel}} + end + + packageId & moduleName & functionName --> newFunction --> function + function & arguments & types --> newMoveCall --> moveCall + moveCall & isFinal --> newTransaction --> transaction + discovery & channel & transaction --> register + +``` + +Note that `new_transaction` takes a vector of `Transaction` objects, so the above is slightly simplified. To allow for complicated call receiving, the `is_final` field of a `Transaction` tells the relayer what to do with a given `Transaction` . + +When a `Transaction` is final, the relayer will execute it. Note that during the transaction the application needs to consume the `ApprovedCall` object: + +```mermaid +graph +subgraph user + channel{{GatewayChannel}} +end +subgraph hotpotato[Hot Potato] + ApprovedCall((ApprovedCall)) +end + +consume([axelar::channel::consume_approved_call]) +channel & ApprovedCall --> consume --> source_chain[/source_chain: String/] & source_address[/source_address: String/] & payload[/payload: vector<u8>/] + +``` From 919860f961d294618d50595685f7ee7cf116020e Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 7 Nov 2024 20:43:25 +0200 Subject: [PATCH 09/13] Add a table with limitations and some more scripts to limits. Also moved relayer discovery to move dir --- .github/workflows/limits.yaml | 53 ---------- INTEGRATION.md | 133 ++----------------------- move/relayer_discovery/README.md | 166 +++++++++++++++++++++++++++++++ scripts/limits.js | 135 +++++++++++++++++++++---- 4 files changed, 292 insertions(+), 195 deletions(-) delete mode 100644 .github/workflows/limits.yaml create mode 100644 move/relayer_discovery/README.md diff --git a/.github/workflows/limits.yaml b/.github/workflows/limits.yaml deleted file mode 100644 index 9680da6c..00000000 --- a/.github/workflows/limits.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: Find Limits - -on: pull_request - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - limits: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup tools - uses: ./.github/actions/setup-tools - with: - install-sui: 'true' - install-nodejs: 'true' - - - name: Build TS and Move Modules - run: npm run build - - - name: Initialize Sui Config - run: echo -e "y\n\n1" | sui client envs - - - name: Run Localnet - run: nohup sh -c "RUST_LOG="off,sui_node=info" sui start --with-faucet --force-regenesis" > nohup.out 2> nohup.err < /dev/null & - - - name: Sleep for 30 seconds - run: sleep 30s - shell: bash - - - name: Create limits - run: node scripts/limits.js >> comment.md - - - name: Find Code Coverage comment - uses: peter-evans/find-comment@v3 - id: find-comment - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Code Coverage Summary - - - name: Create or update Code Coverage comment - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.find-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body-path: ./comment.md - edit-mode: replace diff --git a/INTEGRATION.md b/INTEGRATION.md index b244553d..d68646aa 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -4,6 +4,16 @@ Sui is a smart contract chain that offers some unique design parameters. For thi ## General Limitations +| Limit | Minimum | Recommended | Sui Gateway | +|-|-|-|-| +| Cross-chain Message Size | 16 KB | 64 KB | 16 KB | +| Chain Name Length | < 20 ASCII chars | - | - | +| Signer Set Size | 40 signers | 100 signers | 313 | +| Signature Verification | 27 signatures | 67 signatures | 140 | +| Message Approval Batching | 1 | Configurable | > 105 | +| Storage Limit for Messages | Practically unlimited (2^64) | Practically unlimited (2^64) | Practically unlimited | +| Event Retention | 2 weeks | 2 months | Unlimited | + ### Design Limitation There are quite a few differences between EVM and Sui. @@ -106,126 +116,3 @@ public struct Transaction has store, copy, drop { ``` As seen above there are some options to allow for dynamic objects to be referenced that could not be known on chain, since they do not exist on chain (the `ApprovedCall` object, the payload of the incoming call and the returned arguments of a previous `MoveCall`). - -### Relayer - -First the relayer needs to get a contract call approved: - -```mermaid -graph - process([axelar::gateway::process_commands]) - -subgraph tx1[Get Approval] - subgraph pure1[pure] - input[/commands input: vector<u8>/] - end - subgraph public0[public] - gateway{Gateway} - end - input & gateway --> process -end -``` - -Next, they need to figure out what to actually call to execute: - -```mermaid -graph - getTransaction([axelar::discovery::get_transaction]) - - subgraph public1[pubic] - discovery{RelayerDiscovery} - end - transaction[/Transaction/] - channelId[/Channel Id/] - discovery & channelId --> getTransaction --> devInspect[Dev Inspect] --> transaction - - transaction --> |is_final == true| block[Ready to Execute] - transaction --> |is_final == false| devInspect[Dev Inspect] - -``` - -Finally, they need to get the `ApprovedCall` object from the gateway and use it to execute: - -```mermaid -graph - subgraph public - Gateway{Gateway} - end - - subgraph pure - commandId[/command_id: vector<u8>/] - sourceChain[/source_chain: String/] - sourceAddress[/source_address: String/] - destinationAddress[/destination_channel_id: address/] - payload[/payload: vector<u8>/] - end - subgraph hotpotato[Hot Potato] - ApprovedCall((ApprovedCall)) - end - takeCall([axelar::gateway::takeContractCall]) - Gateway & commandId & sourceChain & sourceAddress & destinationAddress & payload --> takeCall --> ApprovedCall - ApprovedCall --> execute[Execute As Described by `Transaction` Above] - -``` - -### Application - -Anyone can register a `Transaction` for their channel using `axelar::discovery::register_transaction`: - -```mermaid -graph - register([axelar::discovery::register_transaction]) - newFunction([axelar::discovery::new_function]) - newMoveCall([axelar::discovery::new_move_call]) - newTransaction([axelar::discovery::new_transaction]) - - - function[/Function/] - moveCall[/MoveCall/] - transaction[/Transaction/] - - subgraph pure1[pure] - packageId[/package id: address/] - moduleName[/module name: String/] - functionName[/function name: String/] - end - subgraph pure2[pure] - arguments[/arguments: vector<vector<u8>>/] - types[/type aguments: vector<String>/] - end - subgraph pure3[pure] - isFinal[is_final: bool] - end - - subgraph public - discovery{RelayerDiscovery} - end - - subgraph user - channel{{GatewayChannel}} - end - - packageId & moduleName & functionName --> newFunction --> function - function & arguments & types --> newMoveCall --> moveCall - moveCall & isFinal --> newTransaction --> transaction - discovery & channel & transaction --> register - -``` - -Note that `new_transaction` takes a vector of `Transaction` objects, so the above is slightly simplified. To allow for complicated call receiving, the `is_final` field of a `Transaction` tells the relayer what to do with a given `Transaction` . - -When a `Transaction` is final, the relayer will execute it. Note that during the transaction the application needs to consume the `ApprovedCall` object: - -```mermaid -graph -subgraph user - channel{{GatewayChannel}} -end -subgraph hotpotato[Hot Potato] - ApprovedCall((ApprovedCall)) -end - -consume([axelar::channel::consume_approved_call]) -channel & ApprovedCall --> consume --> source_chain[/source_chain: String/] & source_address[/source_address: String/] & payload[/payload: vector<u8>/] - -``` diff --git a/move/relayer_discovery/README.md b/move/relayer_discovery/README.md new file mode 100644 index 00000000..b983338a --- /dev/null +++ b/move/relayer_discovery/README.md @@ -0,0 +1,166 @@ +# Relayer Discovery + +The only information about an incoming contract call through axelar are + +- Command Id: A unique identifier for the command approving the call, 32 bytes long. +- Source Chain: The name of the source chain, as a String +- Source Address: The caller, as a String. +- Destination Chain: “Sui” for the purposes of this document +- Destination Address: The destination, a Sui address. + +As we already explained, the destination address will be the `ID` of a `GatewayChannel` object that needs to be provided to consume the call. However there is no way for a relayer to know what they are supposed to call to get the call to be consumed, since they should not have access to the `GatewayChannel` object itself. This creates the need to make the destination for each incoming call publicly known so that any relayer can query it. + +## Transaction Object + +The following objects are defined in the `axelar::discovery` module and can be used to register/decode transactions. + +```rust +struct Function has store, copy, drop { + package_id: address, + module_name: String, + name: String, +} + +/// Arguments are prefixed with: +/// - 0 for objects followed by exactly 32 bytes that cointain the object id +/// - 1 for pures followed by the bcs encoded form of the pure object +/// - 2 for the ApprovedCall object, followed by nothing (to be passed into the target function) +/// - 3 for the payload of the contract call (to be passed into the intermediate function) +/// - 4 for an argument returned from a previous move call, followed by a u8 specified which call to get the return of (0 for the first transaction AFTER the one that gets ApprovedCall out), and then another u8 specifying which argument to input. +struct MoveCall has store, copy, drop { + function: Function, + arguments: vector>, + type_arguments: vector, +} + +public struct Transaction has store, copy, drop { + is_final: bool, + move_calls: vector, +} +``` + +As seen above there are some options to allow for dynamic objects to be referenced that could not be known on chain, since they do not exist on chain (the `ApprovedCall` object, the payload of the incoming call and the returned arguments of a previous `MoveCall`). + +## Relayer Discovery + +First the relayer needs to get a contract call approved: + +```mermaid +graph + process([axelar::gateway::process_commands]) + +subgraph tx1[Get Approval] + subgraph pure1[pure] + input[/commands input: vector<u8>/] + end + subgraph public0[public] + gateway{Gateway} + end + input & gateway --> process +end +``` + +Next, they need to figure out what to actually call to execute: + +```mermaid +graph + getTransaction([axelar::discovery::get_transaction]) + + subgraph public1[pubic] + discovery{RelayerDiscovery} + end + transaction[/Transaction/] + channelId[/Channel Id/] + discovery & channelId --> getTransaction --> devInspect[Dev Inspect] --> transaction + + transaction --> |is_final == true| block[Ready to Execute] + transaction --> |is_final == false| devInspect[Dev Inspect] + +``` + +Finally, they need to get the `ApprovedCall` object from the gateway and use it to execute: + +```mermaid +graph + subgraph public + Gateway{Gateway} + end + + subgraph pure + commandId[/command_id: vector<u8>/] + sourceChain[/source_chain: String/] + sourceAddress[/source_address: String/] + destinationAddress[/destination_channel_id: address/] + payload[/payload: vector<u8>/] + end + subgraph hotpotato[Hot Potato] + ApprovedCall((ApprovedCall)) + end + takeCall([axelar::gateway::takeContractCall]) + Gateway & commandId & sourceChain & sourceAddress & destinationAddress & payload --> takeCall --> ApprovedCall + ApprovedCall --> execute[Execute As Described by `Transaction` Above] + +``` + +## Application + +Anyone can register a `Transaction` for their channel using `axelar::discovery::register_transaction`: + +```mermaid +graph + register([axelar::discovery::register_transaction]) + newFunction([axelar::discovery::new_function]) + newMoveCall([axelar::discovery::new_move_call]) + newTransaction([axelar::discovery::new_transaction]) + + + function[/Function/] + moveCall[/MoveCall/] + transaction[/Transaction/] + + subgraph pure1[pure] + packageId[/package id: address/] + moduleName[/module name: String/] + functionName[/function name: String/] + end + subgraph pure2[pure] + arguments[/arguments: vector<vector<u8>>/] + types[/type aguments: vector<String>/] + end + subgraph pure3[pure] + isFinal[is_final: bool] + end + + subgraph public + discovery{RelayerDiscovery} + end + + subgraph user + channel{{GatewayChannel}} + end + + packageId & moduleName & functionName --> newFunction --> function + function & arguments & types --> newMoveCall --> moveCall + moveCall & isFinal --> newTransaction --> transaction + discovery & channel & transaction --> register + +``` + +Note that `new_transaction` takes a vector of `Transaction` objects, so the above is slightly simplified. To allow for complicated call receiving, the `is_final` field of a `Transaction` tells the relayer what to do with a given `Transaction` . + +When a `Transaction` is final, the relayer will execute it. Note that during the transaction the application needs to consume the `ApprovedCall` object: + +```mermaid +graph +subgraph user + channel{{GatewayChannel}} +end +subgraph hotpotato[Hot Potato] + ApprovedCall((ApprovedCall)) +end + +consume([axelar::channel::consume_approved_call]) +channel & ApprovedCall --> consume --> source_chain[/source_chain: String/] & source_address[/source_address: String/] & payload[/payload: vector<u8>/] + +``` + diff --git a/scripts/limits.js b/scripts/limits.js index e9feba2c..b8c8b8c4 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -2,16 +2,18 @@ const { SuiClient, getFullnodeUrl } = require('@mysten/sui/client'); const { Ed25519Keypair } = require('@mysten/sui/keypairs/ed25519'); const { Secp256k1Keypair } = require('@mysten/sui/keypairs/secp256k1'); const { requestSuiFromFaucetV0, getFaucetHost } = require('@mysten/sui/faucet'); -const { publishPackage, getRandomBytes32, approveMessage, hashMessage, signMessage } = require('../test/testutils'); +const { publishPackage, getRandomBytes32, approveMessage, hashMessage, signMessage, findObjectId } = require('../test/testutils'); const { TxBuilder, CLOCK_PACKAGE_ID } = require('../dist/cjs'); const { bcsStructs: { - gateway: { WeightedSigners, MessageToSign, Proof }, + gateway: { WeightedSigners, MessageToSign, Proof, Message }, }, } = require('../dist/cjs/bcs'); const { arrayify, hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); +const { bcs } = require('@mysten/sui/bcs'); const COMMAND_TYPE_ROTATE_SIGNERS = 1; +const COMMAND_TYPE_APPROVE_MESSAGES = 0; let client; const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); @@ -26,7 +28,7 @@ let discovery; let gatewayInfo = {}; const discoveryInfo = {}; -function calculateNextSigners(n = 3) { +function calculateNextSigners(n = 3, threshold = 2) { signerKeys = Array.from({length: n}, getRandomBytes32); const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); const keys = signerKeys.map((key, index) => { @@ -46,12 +48,48 @@ function calculateNextSigners(n = 3) { signers: keys.map((key) => { return { pub_key: key.pubKey, weight: 1 }; }), - threshold: 2, + threshold, nonce: hexlify([++nonce]), }, } } +async function rotateSigners(n = 3, threshold = 2) {console.log(n, threshold); + let proofSigners = gatewayInfo.signers; + let proofKeys = gatewayInfo.signerKeys; + let nextSigners = calculateNextSigners(n, threshold); + + const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); + + const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); + + const message = MessageToSign.serialize({ + domain_separator: domainSeparator, + signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), + data_hash: hashed, + }).toBytes(); + + const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); + const encodedProof = Proof.serialize({ + signers: proofSigners, + signatures, + }).toBytes(); + + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::rotate_signers`, + arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], + }); + + await builder.signAndExecute(keypair); + gatewayInfo = { + ...gatewayInfo, + ...nextSigners, + }; + console.log(gatewayInfo, nextSigners); +} + // Perform a binary search to find the maximum possible balue of n before testFn(n) fails. async function binarySearch(testFn, min = 1, max = 10000) { if(max === min + 1) return min @@ -59,8 +97,11 @@ async function binarySearch(testFn, min = 1, max = 10000) { const mid = Math.floor((min + max)/2); try { await testFn(mid); + console.log(`${mid}: success.`); min = mid; } catch(e) { + console.log(`${mid}: failure.`); + console.log(e); max = mid; } } @@ -76,14 +117,29 @@ const previousSignersRetention = 15; async function getMaxSigners() { await sleep(2000); - const proofSigners = gatewayInfo.signers; - const proofKeys = gatewayInfo.signerKeys; + return await binarySearch(rotateSigners); +} + +// This does not work properly because once you rotate to a signer set that cannot sign you are locked out of the gateway. +async function getMaxSignatures() { + await sleep(2000); + return await binarySearch(async (n) => { - let nextSigners = calculateNextSigners(n); + await rotateSigners(n, n); + let proofSigners = gatewayInfo.signers; + let proofKeys = gatewayInfo.signerKeys; - const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); + const contractCallInfo = { + source_chain: 'Ethereum', + message_id: 'Message Id', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; - const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); + const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); + + const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); const message = MessageToSign.serialize({ domain_separator: domainSeparator, @@ -91,7 +147,7 @@ async function getMaxSigners() { data_hash: hashed, }).toBytes(); - const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); + const signatures = signMessage(proofKeys, message); const encodedProof = Proof.serialize({ signers: proofSigners, signatures, @@ -100,16 +156,53 @@ async function getMaxSigners() { const builder = new TxBuilder(client); await builder.moveCall({ - target: `${packageId}::gateway::rotate_signers`, - arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], + target: `${packageId}::gateway::approve_messages`, + arguments: [gateway, messageData, encodedProof], }); - await builder.signAndExecute(keypair); - gatewayInfo = { - ...nextSigners, - ...gatewayInfo, - }; + }, 2, 200); +} + +async function getMaxMessageSize() { + + const builder = new TxBuilder(client); + + const channelObj = await builder.moveCall({ + target: `${packageId}::channel::new`, + arguments: [], }); + + builder.tx.transferObjects([channelObj], keypair.toSuiAddress()); + + const response = await builder.signAndExecute(keypair); + + const channel = findObjectId(response, 'channel::Channel'); + + return await binarySearch(async (n) => { + const payload = new Uint8Array(n); + + const builder = new TxBuilder(client); + + const message = await builder.moveCall({ + target: `${packageId}::gateway::prepare_message`, + arguments: [ + channel, + 'destination_chain', + 'destination_address', + payload, + ] + }); + + await builder.moveCall({ + target: `${packageId}::gateway::send_message`, + arguments: [ + gateway, + message, + ] + }); + + await builder.signAndExecute(keypair); + }, 1000, 1000000); } async function getMaxApprovals() { @@ -191,11 +284,15 @@ async function prepare() { (async() => { await prepare(); const maxApprovals = await getMaxApprovals(); - const maxSigenrs = await getMaxSigners(); + const maxSigners = await getMaxSigners(); + const messageSize = await getMaxMessageSize(); + const maxSignatures = await getMaxSignatures(); console.log("
"); console.log(" Click to see the limis") console.log(`Maximum possible approvals in a call: ${maxApprovals}\n`); - console.log(`Maximum possible signers in a signer set: ${maxSigenrs}`); + console.log(`Maximum possible signers in a signer set: ${maxSigners}\n`); + console.log(`Maximum message size: ${messageSize}\n`); + console.log(`Maximum Signatures: ${maxSignatures}\n`); console.log("
"); })(); From 4da186ceabf9425c43b700f43df62623aef19b5b Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 18 Nov 2024 16:28:12 +0200 Subject: [PATCH 10/13] Update scripts/limits.js Co-authored-by: Milap Sheth --- scripts/limits.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/limits.js b/scripts/limits.js index b8c8b8c4..539323ca 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -12,8 +12,8 @@ const { const { arrayify, hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); const { bcs } = require('@mysten/sui/bcs'); -const COMMAND_TYPE_ROTATE_SIGNERS = 1; const COMMAND_TYPE_APPROVE_MESSAGES = 0; +const COMMAND_TYPE_ROTATE_SIGNERS = 1; let client; const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); From 112dbde60b3f0939febc04f444908c9d89e2076e Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 18 Nov 2024 16:34:11 +0200 Subject: [PATCH 11/13] change the format fo limits.js a bit --- scripts/limits.js | 458 +++++++++++++++++++++++----------------------- 1 file changed, 230 insertions(+), 228 deletions(-) diff --git a/scripts/limits.js b/scripts/limits.js index 539323ca..29889501 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -12,134 +12,57 @@ const { const { arrayify, hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); const { bcs } = require('@mysten/sui/bcs'); -const COMMAND_TYPE_APPROVE_MESSAGES = 0; -const COMMAND_TYPE_ROTATE_SIGNERS = 1; - -let client; -const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); -const deployer = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); -const keypair = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); -const domainSeparator = getRandomBytes32(); -const network = process.env.NETWORK || 'localnet'; -let nonce = 0; -let packageId; -let gateway; -let discovery; -let gatewayInfo = {}; -const discoveryInfo = {}; - -function calculateNextSigners(n = 3, threshold = 2) { - signerKeys = Array.from({length: n}, getRandomBytes32); - const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); - const keys = signerKeys.map((key, index) => { - return { privKey: key, pubKey: pubKeys[index] }; - }); - keys.sort((key1, key2) => { - for (let i = 0; i < 33; i++) { - if (key1.pubKey[i] < key2.pubKey[i]) return -1; - if (key1.pubKey[i] > key2.pubKey[i]) return 1; - } - - return 0; - }); - return { - signerKeys: keys.map((key) => key.privKey), - signers: { - signers: keys.map((key) => { - return { pub_key: key.pubKey, weight: 1 }; - }), - threshold, - nonce: hexlify([++nonce]), - }, - } -} - -async function rotateSigners(n = 3, threshold = 2) {console.log(n, threshold); - let proofSigners = gatewayInfo.signers; - let proofKeys = gatewayInfo.signerKeys; - let nextSigners = calculateNextSigners(n, threshold); - - const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); - - const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); - - const message = MessageToSign.serialize({ - domain_separator: domainSeparator, - signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), - data_hash: hashed, - }).toBytes(); - - const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); - const encodedProof = Proof.serialize({ - signers: proofSigners, - signatures, - }).toBytes(); - - const builder = new TxBuilder(client); - - await builder.moveCall({ - target: `${packageId}::gateway::rotate_signers`, - arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], - }); - - await builder.signAndExecute(keypair); - gatewayInfo = { - ...gatewayInfo, - ...nextSigners, - }; - console.log(gatewayInfo, nextSigners); -} +async function main() { + const COMMAND_TYPE_APPROVE_MESSAGES = 0; + const COMMAND_TYPE_ROTATE_SIGNERS = 1; + + let client; + const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); + const deployer = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); + const keypair = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); + const domainSeparator = getRandomBytes32(); + const network = process.env.NETWORK || 'localnet'; + let nonce = 0; + let packageId; + let gateway; + let discovery; + let gatewayInfo = {}; + const discoveryInfo = {}; + + function calculateNextSigners(n = 3, threshold = 2) { + signerKeys = Array.from({length: n}, getRandomBytes32); + const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); + const keys = signerKeys.map((key, index) => { + return { privKey: key, pubKey: pubKeys[index] }; + }); + keys.sort((key1, key2) => { + for (let i = 0; i < 33; i++) { + if (key1.pubKey[i] < key2.pubKey[i]) return -1; + if (key1.pubKey[i] > key2.pubKey[i]) return 1; + } -// Perform a binary search to find the maximum possible balue of n before testFn(n) fails. -async function binarySearch(testFn, min = 1, max = 10000) { - if(max === min + 1) return min - while(max - min > 1) { - const mid = Math.floor((min + max)/2); - try { - await testFn(mid); - console.log(`${mid}: success.`); - min = mid; - } catch(e) { - console.log(`${mid}: failure.`); - console.log(e); - max = mid; + return 0; + }); + return { + signerKeys: keys.map((key) => key.privKey), + signers: { + signers: keys.map((key) => { + return { pub_key: key.pubKey, weight: 1 }; + }), + threshold, + nonce: hexlify([++nonce]), + }, } } - return min; -} -async function sleep(ms = 1000) { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - -const minimumRotationDelay = 1000; -const previousSignersRetention = 15; - -async function getMaxSigners() { - await sleep(2000); - return await binarySearch(rotateSigners); -} - -// This does not work properly because once you rotate to a signer set that cannot sign you are locked out of the gateway. -async function getMaxSignatures() { - await sleep(2000); - - return await binarySearch(async (n) => { - await rotateSigners(n, n); + async function rotateSigners(n = 3, threshold = 2) {console.log(n, threshold); let proofSigners = gatewayInfo.signers; let proofKeys = gatewayInfo.signerKeys; + let nextSigners = calculateNextSigners(n, threshold); - const contractCallInfo = { - source_chain: 'Ethereum', - message_id: 'Message Id', - source_address: 'Source Address', - destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), - payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), - }; - - const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); + const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); - const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); + const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); const message = MessageToSign.serialize({ domain_separator: domainSeparator, @@ -147,7 +70,7 @@ async function getMaxSignatures() { data_hash: hashed, }).toBytes(); - const signatures = signMessage(proofKeys, message); + const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); const encodedProof = Proof.serialize({ signers: proofSigners, signatures, @@ -156,132 +79,209 @@ async function getMaxSignatures() { const builder = new TxBuilder(client); await builder.moveCall({ - target: `${packageId}::gateway::approve_messages`, - arguments: [gateway, messageData, encodedProof], + target: `${packageId}::gateway::rotate_signers`, + arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], }); - await builder.signAndExecute(keypair); - }, 2, 200); -} -async function getMaxMessageSize() { + await builder.signAndExecute(keypair); + gatewayInfo = { + ...gatewayInfo, + ...nextSigners, + }; + console.log(gatewayInfo, nextSigners); + } - const builder = new TxBuilder(client); + // Perform a binary search to find the maximum possible balue of n before testFn(n) fails. + async function binarySearch(testFn, min = 1, max = 10000) { + if(max === min + 1) return min + while(max - min > 1) { + const mid = Math.floor((min + max)/2); + try { + await testFn(mid); + console.log(`${mid}: success.`); + min = mid; + } catch(e) { + console.log(`${mid}: failure.`); + console.log(e); + max = mid; + } + } + return min; + } - const channelObj = await builder.moveCall({ - target: `${packageId}::channel::new`, - arguments: [], - }); + async function sleep(ms = 1000) { + await new Promise((resolve) => setTimeout(resolve, ms)); + } - builder.tx.transferObjects([channelObj], keypair.toSuiAddress()); + const minimumRotationDelay = 1000; + const previousSignersRetention = 15; - const response = await builder.signAndExecute(keypair); + async function getMaxSigners() { + await sleep(2000); + return await binarySearch(rotateSigners); + } - const channel = findObjectId(response, 'channel::Channel'); + // This does not work properly because once you rotate to a signer set that cannot sign you are locked out of the gateway. + async function getMaxSignatures() { + await sleep(2000); + + return await binarySearch(async (n) => { + await rotateSigners(n, n); + let proofSigners = gatewayInfo.signers; + let proofKeys = gatewayInfo.signerKeys; + + const contractCallInfo = { + source_chain: 'Ethereum', + message_id: 'Message Id', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; + + const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); + + const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); + + const message = MessageToSign.serialize({ + domain_separator: domainSeparator, + signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), + data_hash: hashed, + }).toBytes(); + + const signatures = signMessage(proofKeys, message); + const encodedProof = Proof.serialize({ + signers: proofSigners, + signatures, + }).toBytes(); + + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::approve_messages`, + arguments: [gateway, messageData, encodedProof], + }); + await builder.signAndExecute(keypair); + }, 2, 200); + } - return await binarySearch(async (n) => { - const payload = new Uint8Array(n); + async function getMaxMessageSize() { const builder = new TxBuilder(client); - const message = await builder.moveCall({ - target: `${packageId}::gateway::prepare_message`, - arguments: [ - channel, - 'destination_chain', - 'destination_address', - payload, - ] + const channelObj = await builder.moveCall({ + target: `${packageId}::channel::new`, + arguments: [], }); + builder.tx.transferObjects([channelObj], keypair.toSuiAddress()); + + const response = await builder.signAndExecute(keypair); + + const channel = findObjectId(response, 'channel::Channel'); + + return await binarySearch(async (n) => { + const payload = new Uint8Array(n); + + const builder = new TxBuilder(client); + + const message = await builder.moveCall({ + target: `${packageId}::gateway::prepare_message`, + arguments: [ + channel, + 'destination_chain', + 'destination_address', + payload, + ] + }); + + await builder.moveCall({ + target: `${packageId}::gateway::send_message`, + arguments: [ + gateway, + message, + ] + }); + + await builder.signAndExecute(keypair); + }, 1000, 1000000); + } + + async function getMaxApprovals() { + const message = { + source_chain: 'Ethereum', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; + + return await binarySearch(async (n) => { + const messages = Array.from({length: n}, (_, index) => { + return { + message_id: getRandomBytes32(), + ...message, + } + }); + await approveMessage(client, keypair, gatewayInfo, messages); + }); + } + + async function prepare() { + client = new SuiClient({ url: getFullnodeUrl(network) }); + + await Promise.all( + [operator, deployer, keypair].map((keypair) => + requestSuiFromFaucetV0({ + host: getFaucetHost(network), + recipient: keypair.toSuiAddress(), + }), + ), + ); + + await publishPackage(client, deployer, 'utils'); + await publishPackage(client, deployer, 'version_control'); + let result = await publishPackage(client, deployer, 'axelar_gateway'); + packageId = result.packageId; + const creatorCap = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${packageId}::gateway::CreatorCap`, + ).objectId; + result = await publishPackage(client, deployer, 'relayer_discovery'); + const discoveryPackageId = result.packageId; + discovery = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${discoveryPackageId}::discovery::RelayerDiscovery`, + ).objectId; + + gatewayInfo = { + ...calculateNextSigners(), + ... gatewayInfo, + }; + + const encodedSigners = WeightedSigners.serialize(gatewayInfo.signers).toBytes(); + const builder = new TxBuilder(client); + await builder.moveCall({ - target: `${packageId}::gateway::send_message`, + target: `${packageId}::gateway::setup`, arguments: [ - gateway, - message, - ] + creatorCap, + operator.toSuiAddress(), + domainSeparator, + minimumRotationDelay, + previousSignersRetention, + encodedSigners, + CLOCK_PACKAGE_ID, + ], }); - await builder.signAndExecute(keypair); - }, 1000, 1000000); -} + result = await builder.signAndExecute(deployer); -async function getMaxApprovals() { - const message = { - source_chain: 'Ethereum', - source_address: 'Source Address', - destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), - payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), - }; - - return await binarySearch(async (n) => { - const messages = Array.from({length: n}, (_, index) => { - return { - message_id: getRandomBytes32(), - ...message, - } - }); - await approveMessage(client, keypair, gatewayInfo, messages); - }); -} + gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`).objectId; -async function prepare() { - client = new SuiClient({ url: getFullnodeUrl(network) }); - - await Promise.all( - [operator, deployer, keypair].map((keypair) => - requestSuiFromFaucetV0({ - host: getFaucetHost(network), - recipient: keypair.toSuiAddress(), - }), - ), - ); - - await publishPackage(client, deployer, 'utils'); - await publishPackage(client, deployer, 'version_control'); - let result = await publishPackage(client, deployer, 'axelar_gateway'); - packageId = result.packageId; - const creatorCap = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${packageId}::gateway::CreatorCap`, - ).objectId; - result = await publishPackage(client, deployer, 'relayer_discovery'); - const discoveryPackageId = result.packageId; - discovery = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${discoveryPackageId}::discovery::RelayerDiscovery`, - ).objectId; - - gatewayInfo = { - ...calculateNextSigners(), - ... gatewayInfo, - }; - - const encodedSigners = WeightedSigners.serialize(gatewayInfo.signers).toBytes(); - const builder = new TxBuilder(client); - - await builder.moveCall({ - target: `${packageId}::gateway::setup`, - arguments: [ - creatorCap, - operator.toSuiAddress(), - domainSeparator, - minimumRotationDelay, - previousSignersRetention, - encodedSigners, - CLOCK_PACKAGE_ID, - ], - }); - - result = await builder.signAndExecute(deployer); - - gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`).objectId; - - gatewayInfo.gateway = gateway; - gatewayInfo.domainSeparator = domainSeparator; - gatewayInfo.packageId = packageId; - discoveryInfo.packageId = discoveryPackageId; - discoveryInfo.discovery = discovery; -} + gatewayInfo.gateway = gateway; + gatewayInfo.domainSeparator = domainSeparator; + gatewayInfo.packageId = packageId; + discoveryInfo.packageId = discoveryPackageId; + discoveryInfo.discovery = discovery; + } -(async() => { await prepare(); const maxApprovals = await getMaxApprovals(); const maxSigners = await getMaxSigners(); @@ -294,7 +294,9 @@ async function prepare() { console.log(`Maximum message size: ${messageSize}\n`); console.log(`Maximum Signatures: ${maxSignatures}\n`); console.log(""); -})(); +} + +main(); From adb738fbd29217f0ef66138d2957ae1dfe9434f7 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 18 Nov 2024 16:35:19 +0200 Subject: [PATCH 12/13] addressed some comments --- scripts/limits.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/limits.js b/scripts/limits.js index 29889501..138cf024 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -91,10 +91,10 @@ async function main() { console.log(gatewayInfo, nextSigners); } - // Perform a binary search to find the maximum possible balue of n before testFn(n) fails. + // Perform a binary search to find the maximum possible value of n before testFn(n) fails. async function binarySearch(testFn, min = 1, max = 10000) { if(max === min + 1) return min - while(max - min > 1) { + while (max - min > 1) { const mid = Math.floor((min + max)/2); try { await testFn(mid); @@ -165,7 +165,6 @@ async function main() { } async function getMaxMessageSize() { - const builder = new TxBuilder(client); const channelObj = await builder.moveCall({ From 1878da8804c899e7108533bbf5c8d22b28f5bdba Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 19 Nov 2024 19:49:50 +0200 Subject: [PATCH 13/13] refactor limits script again --- scripts/limits.js | 455 +++++++++++++++++++++++----------------------- 1 file changed, 229 insertions(+), 226 deletions(-) diff --git a/scripts/limits.js b/scripts/limits.js index 138cf024..b2fb0bcf 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -12,57 +12,117 @@ const { const { arrayify, hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); const { bcs } = require('@mysten/sui/bcs'); -async function main() { - const COMMAND_TYPE_APPROVE_MESSAGES = 0; - const COMMAND_TYPE_ROTATE_SIGNERS = 1; +const COMMAND_TYPE_APPROVE_MESSAGES = 0; +const COMMAND_TYPE_ROTATE_SIGNERS = 1; +const minimumRotationDelay = 1000; +const previousSignersRetention = 15; + +function calculateNextSigners(gatewayInfo, n = 3, threshold = 2) { + signerKeys = Array.from({length: n}, getRandomBytes32); + const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); + const keys = signerKeys.map((key, index) => { + return { privKey: key, pubKey: pubKeys[index] }; + }); + keys.sort((key1, key2) => { + for (let i = 0; i < 33; i++) { + if (key1.pubKey[i] < key2.pubKey[i]) return -1; + if (key1.pubKey[i] > key2.pubKey[i]) return 1; + } - let client; - const operator = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); - const deployer = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); - const keypair = Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32())); - const domainSeparator = getRandomBytes32(); - const network = process.env.NETWORK || 'localnet'; - let nonce = 0; - let packageId; - let gateway; - let discovery; - let gatewayInfo = {}; - const discoveryInfo = {}; - - function calculateNextSigners(n = 3, threshold = 2) { - signerKeys = Array.from({length: n}, getRandomBytes32); - const pubKeys = signerKeys.map((key) => Secp256k1Keypair.fromSecretKey(arrayify(key)).getPublicKey().toRawBytes()); - const keys = signerKeys.map((key, index) => { - return { privKey: key, pubKey: pubKeys[index] }; - }); - keys.sort((key1, key2) => { - for (let i = 0; i < 33; i++) { - if (key1.pubKey[i] < key2.pubKey[i]) return -1; - if (key1.pubKey[i] > key2.pubKey[i]) return 1; - } + return 0; + }); + return { + signerKeys: keys.map((key) => key.privKey), + signers: { + signers: keys.map((key) => { + return { pub_key: key.pubKey, weight: 1 }; + }), + threshold, + nonce: hexlify([++gatewayInfo.nonce]), + }, + } +} - return 0; - }); - return { - signerKeys: keys.map((key) => key.privKey), - signers: { - signers: keys.map((key) => { - return { pub_key: key.pubKey, weight: 1 }; - }), - threshold, - nonce: hexlify([++nonce]), - }, +async function rotateSigners(client, keypair, gatewayInfo, n = 3, threshold = 2) { + let proofSigners = gatewayInfo.signers; + let proofKeys = gatewayInfo.signerKeys; + let nextSigners = calculateNextSigners(gatewayInfo, n, threshold); + + const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); + + const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); + + const message = MessageToSign.serialize({ + domain_separator: domainSeparator, + signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), + data_hash: hashed, + }).toBytes(); + + const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); + const encodedProof = Proof.serialize({ + signers: proofSigners, + signatures, + }).toBytes(); + + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::rotate_signers`, + arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], + }); + + await builder.signAndExecute(keypair); + gatewayInfo = { + ...gatewayInfo, + ...nextSigners, + }; +} + +// Perform a binary search to find the maximum possible value of n before testFn(n) fails. +async function binarySearch(testFn, min = 1, max = 10000) { + if(max === min + 1) return min + while (max - min > 1) { + const mid = Math.floor((min + max)/2); + try { + await testFn(mid); + min = mid; + } catch(e) { + max = mid; } } + return min; +} + +async function sleep(ms = 1000) { + await new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function getMaxSigners(client, keypair, gatewayInfo) { + await sleep(2000); + return await binarySearch(() => rotateSigners(client, keypair, gatewayInfo)); +} - async function rotateSigners(n = 3, threshold = 2) {console.log(n, threshold); + +// This does not work properly because once you rotate to a signer set that cannot sign you are locked out of the gateway. +async function getMaxSignatures(client, keypair) { + await sleep(2000); + + return await binarySearch(async (n) => { + await rotateSigners(client, keypair, gatewayInfo, n, n); let proofSigners = gatewayInfo.signers; let proofKeys = gatewayInfo.signerKeys; - let nextSigners = calculateNextSigners(n, threshold); - const encodedSigners = WeightedSigners.serialize(nextSigners.signers).toBytes(); + const contractCallInfo = { + source_chain: 'Ethereum', + message_id: 'Message Id', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; + + const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); - const hashed = hashMessage(encodedSigners, COMMAND_TYPE_ROTATE_SIGNERS); + const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); const message = MessageToSign.serialize({ domain_separator: domainSeparator, @@ -70,7 +130,7 @@ async function main() { data_hash: hashed, }).toBytes(); - const signatures = signMessage(proofKeys.slice(0, proofSigners.threshold), message); + const signatures = signMessage(proofKeys, message); const encodedProof = Proof.serialize({ signers: proofSigners, signatures, @@ -79,213 +139,156 @@ async function main() { const builder = new TxBuilder(client); await builder.moveCall({ - target: `${packageId}::gateway::rotate_signers`, - arguments: [gateway, CLOCK_PACKAGE_ID, encodedSigners, encodedProof], + target: `${packageId}::gateway::approve_messages`, + arguments: [gateway, messageData, encodedProof], }); - await builder.signAndExecute(keypair); - gatewayInfo = { - ...gatewayInfo, - ...nextSigners, - }; - console.log(gatewayInfo, nextSigners); - } + }, 2, 200); +} - // Perform a binary search to find the maximum possible value of n before testFn(n) fails. - async function binarySearch(testFn, min = 1, max = 10000) { - if(max === min + 1) return min - while (max - min > 1) { - const mid = Math.floor((min + max)/2); - try { - await testFn(mid); - console.log(`${mid}: success.`); - min = mid; - } catch(e) { - console.log(`${mid}: failure.`); - console.log(e); - max = mid; - } - } - return min; - } +async function getMaxMessageSize(client, keypair, packageId, gateway) { + const builder = new TxBuilder(client); - async function sleep(ms = 1000) { - await new Promise((resolve) => setTimeout(resolve, ms)); - } + const channelObj = await builder.moveCall({ + target: `${packageId}::channel::new`, + arguments: [], + }); - const minimumRotationDelay = 1000; - const previousSignersRetention = 15; + builder.tx.transferObjects([channelObj], keypair.toSuiAddress()); - async function getMaxSigners() { - await sleep(2000); - return await binarySearch(rotateSigners); - } + const response = await builder.signAndExecute(keypair); - // This does not work properly because once you rotate to a signer set that cannot sign you are locked out of the gateway. - async function getMaxSignatures() { - await sleep(2000); - - return await binarySearch(async (n) => { - await rotateSigners(n, n); - let proofSigners = gatewayInfo.signers; - let proofKeys = gatewayInfo.signerKeys; - - const contractCallInfo = { - source_chain: 'Ethereum', - message_id: 'Message Id', - source_address: 'Source Address', - destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), - payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), - }; - - const messageData = bcs.vector(Message).serialize([contractCallInfo]).toBytes(); - - const hashed = hashMessage(messageData, COMMAND_TYPE_APPROVE_MESSAGES); - - const message = MessageToSign.serialize({ - domain_separator: domainSeparator, - signers_hash: keccak256(WeightedSigners.serialize(proofSigners).toBytes()), - data_hash: hashed, - }).toBytes(); - - const signatures = signMessage(proofKeys, message); - const encodedProof = Proof.serialize({ - signers: proofSigners, - signatures, - }).toBytes(); - - const builder = new TxBuilder(client); - - await builder.moveCall({ - target: `${packageId}::gateway::approve_messages`, - arguments: [gateway, messageData, encodedProof], - }); - await builder.signAndExecute(keypair); - }, 2, 200); - } + const channel = findObjectId(response, 'channel::Channel'); + + return await binarySearch(async (n) => { + const payload = new Uint8Array(n); - async function getMaxMessageSize() { const builder = new TxBuilder(client); - const channelObj = await builder.moveCall({ - target: `${packageId}::channel::new`, - arguments: [], + const message = await builder.moveCall({ + target: `${packageId}::gateway::prepare_message`, + arguments: [ + channel, + 'destination_chain', + 'destination_address', + payload, + ] }); - builder.tx.transferObjects([channelObj], keypair.toSuiAddress()); - - const response = await builder.signAndExecute(keypair); - - const channel = findObjectId(response, 'channel::Channel'); - - return await binarySearch(async (n) => { - const payload = new Uint8Array(n); - - const builder = new TxBuilder(client); + await builder.moveCall({ + target: `${packageId}::gateway::send_message`, + arguments: [ + gateway, + message, + ] + }); - const message = await builder.moveCall({ - target: `${packageId}::gateway::prepare_message`, - arguments: [ - channel, - 'destination_chain', - 'destination_address', - payload, - ] - }); + await builder.signAndExecute(keypair); + }, 1000, 1000000); +} - await builder.moveCall({ - target: `${packageId}::gateway::send_message`, - arguments: [ - gateway, - message, - ] - }); +async function getMaxApprovals(client, keypair, gatewayInfo) { + const message = { + source_chain: 'Ethereum', + source_address: 'Source Address', + destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), + payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), + }; + + return await binarySearch(async (n) => { + const messages = Array.from({length: n}, (_, index) => { + return { + message_id: getRandomBytes32(), + ...message, + } + }); + await approveMessage(client, keypair, gatewayInfo, messages); + }); +} - await builder.signAndExecute(keypair); - }, 1000, 1000000); +async function prepare(network, keypairs, domainSeparator) { + client = new SuiClient({ url: getFullnodeUrl(network) }); + + await Promise.all( + keypairs.map((keypair) => + requestSuiFromFaucetV0({ + host: getFaucetHost(network), + recipient: keypair.toSuiAddress(), + }), + ), + ); + const [operator, deployer] = keypairs; + + await publishPackage(client, deployer, 'utils'); + await publishPackage(client, deployer, 'version_control'); + let result = await publishPackage(client, deployer, 'axelar_gateway'); + packageId = result.packageId; + const creatorCap = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${packageId}::gateway::CreatorCap`, + ).objectId; + result = await publishPackage(client, deployer, 'relayer_discovery'); + const discoveryPackageId = result.packageId; + discovery = result.publishTxn.objectChanges.find( + (change) => change.objectType === `${discoveryPackageId}::discovery::RelayerDiscovery`, + ).objectId; + let gatewayInfo = { nonce: 0 }; + gatewayInfo = { + ...calculateNextSigners(gatewayInfo), + ... gatewayInfo, + }; + + const encodedSigners = WeightedSigners.serialize(gatewayInfo.signers).toBytes(); + const builder = new TxBuilder(client); + + await builder.moveCall({ + target: `${packageId}::gateway::setup`, + arguments: [ + creatorCap, + operator.toSuiAddress(), + domainSeparator, + minimumRotationDelay, + previousSignersRetention, + encodedSigners, + CLOCK_PACKAGE_ID, + ], + }); + + result = await builder.signAndExecute(deployer); + + gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`).objectId; + + gatewayInfo.gateway = gateway; + gatewayInfo.domainSeparator = domainSeparator; + gatewayInfo.packageId = packageId; + const discoveryInfo = { + packageId: discoveryPackageId, + discovery, } - async function getMaxApprovals() { - const message = { - source_chain: 'Ethereum', - source_address: 'Source Address', - destination_id: keccak256(defaultAbiCoder.encode(['string'], ['destination'])), - payload_hash: keccak256(defaultAbiCoder.encode(['string'], ['payload hash'])), - }; - - return await binarySearch(async (n) => { - const messages = Array.from({length: n}, (_, index) => { - return { - message_id: getRandomBytes32(), - ...message, - } - }); - await approveMessage(client, keypair, gatewayInfo, messages); - }); + return { + client, gatewayInfo, discoveryInfo, } +} - async function prepare() { - client = new SuiClient({ url: getFullnodeUrl(network) }); - - await Promise.all( - [operator, deployer, keypair].map((keypair) => - requestSuiFromFaucetV0({ - host: getFaucetHost(network), - recipient: keypair.toSuiAddress(), - }), - ), - ); - - await publishPackage(client, deployer, 'utils'); - await publishPackage(client, deployer, 'version_control'); - let result = await publishPackage(client, deployer, 'axelar_gateway'); - packageId = result.packageId; - const creatorCap = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${packageId}::gateway::CreatorCap`, - ).objectId; - result = await publishPackage(client, deployer, 'relayer_discovery'); - const discoveryPackageId = result.packageId; - discovery = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${discoveryPackageId}::discovery::RelayerDiscovery`, - ).objectId; - - gatewayInfo = { - ...calculateNextSigners(), - ... gatewayInfo, - }; - - const encodedSigners = WeightedSigners.serialize(gatewayInfo.signers).toBytes(); - const builder = new TxBuilder(client); - await builder.moveCall({ - target: `${packageId}::gateway::setup`, - arguments: [ - creatorCap, - operator.toSuiAddress(), - domainSeparator, - minimumRotationDelay, - previousSignersRetention, - encodedSigners, - CLOCK_PACKAGE_ID, - ], - }); +async function main() { + const keypairs = [0, 1, 2].map(() => Ed25519Keypair.fromSecretKey(arrayify(getRandomBytes32()))); + const [, , keypair] = keypairs; - result = await builder.signAndExecute(deployer); + const domainSeparator = getRandomBytes32(); + const network = process.env.NETWORK || 'localnet'; - gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`).objectId; + const { + client, gatewayInfo, + } = await prepare(network, keypairs, domainSeparator); - gatewayInfo.gateway = gateway; - gatewayInfo.domainSeparator = domainSeparator; - gatewayInfo.packageId = packageId; - discoveryInfo.packageId = discoveryPackageId; - discoveryInfo.discovery = discovery; - } + const packageId = gatewayInfo.packageId; + const gateway = gatewayInfo.gateway; - await prepare(); - const maxApprovals = await getMaxApprovals(); - const maxSigners = await getMaxSigners(); - const messageSize = await getMaxMessageSize(); - const maxSignatures = await getMaxSignatures(); + const maxApprovals = await getMaxApprovals(client, keypair, gatewayInfo); + const maxSigners = await getMaxSigners(client, keypair, gatewayInfo); + const messageSize = await getMaxMessageSize(client, keypair, packageId, gateway); + const maxSignatures = await getMaxSignatures(client, keypair); console.log("
"); console.log(" Click to see the limis") console.log(`Maximum possible approvals in a call: ${maxApprovals}\n`);