diff --git a/.changeset/tough-buttons-obey.md b/.changeset/tough-buttons-obey.md new file mode 100644 index 00000000..72645fab --- /dev/null +++ b/.changeset/tough-buttons-obey.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': minor +--- + +Add a query for version on version_control, change CreatorCap to OwnerCap and allow owner to set allowed functions in gateway diff --git a/move/axelar_gateway/sources/gateway.move b/move/axelar_gateway/sources/gateway.move index fe2ebcac..3b0272c5 100644 --- a/move/axelar_gateway/sources/gateway.move +++ b/move/axelar_gateway/sources/gateway.move @@ -41,6 +41,7 @@ use axelar_gateway::bytes32::{Self, Bytes32}; use axelar_gateway::channel::{Channel, ApprovedMessage}; use axelar_gateway::gateway_v0::{Self, Gateway_v0}; use axelar_gateway::message_ticket::{Self, MessageTicket}; +use axelar_gateway::owner_cap::{Self, OwnerCap}; use axelar_gateway::weighted_signers; use std::ascii::{Self, String}; use sui::clock::Clock; @@ -62,30 +63,21 @@ public struct Gateway has key { inner: Versioned, } -// ------------ -// Capabilities -// ------------ -public struct CreatorCap has key, store { - id: UID, -} - // ----- // Setup // ----- -/// Init the module by giving a CreatorCap to the sender to allow a full +/// Init the module by giving a OwnerCap to the sender to allow a full /// `setup`. fun init(ctx: &mut TxContext) { - let cap = CreatorCap { - id: object::new(ctx), - }; + let cap = owner_cap::create(ctx); - transfer::transfer(cap, ctx.sender()); + transfer::public_transfer(cap, ctx.sender()); } /// Setup the module by creating a new Gateway object. entry fun setup( - cap: CreatorCap, + _: &OwnerCap, operator: address, domain_separator: address, minimum_rotation_delay: u64, @@ -94,9 +86,6 @@ entry fun setup( clock: &Clock, ctx: &mut TxContext, ) { - let CreatorCap { id } = cap; - id.delete(); - let inner = versioned::create( VERSION, gateway_v0::new( @@ -185,6 +174,14 @@ entry fun rotate_signers( ) } +entry fun allow_function(self: &mut Gateway, _: &OwnerCap, version: u64, function_name: String) { + self.value_mut!(b"allow_function").allow_function(version, function_name); +} + +entry fun disallow_function(self: &mut Gateway, _: &OwnerCap, version: u64, function_name: String) { + self.value_mut!(b"disallow_function").disallow_function(version, function_name); +} + // ---------------- // Public Functions // ---------------- @@ -274,6 +271,8 @@ fun version_control(): VersionControl { b"is_message_executed", b"take_approved_message", b"send_message", + b"allow_function", + b"disallow_function", ].map!(|function_name| function_name.to_ascii_string()), ]) } @@ -338,6 +337,8 @@ fun dummy(ctx: &mut TxContext): Gateway { b"is_message_executed", b"take_approved_message", b"send_message", + b"allow_function", + b"disallow_function", b"", ].map!(|function_name| function_name.to_ascii_string()), ]), @@ -376,7 +377,7 @@ fun test_init() { init(ts.ctx()); ts.next_tx(@0x0); - let creator_cap = ts.take_from_sender(); + let creator_cap = ts.take_from_sender(); ts.return_to_sender(creator_cap); ts.end(); } @@ -394,14 +395,12 @@ fun test_setup() { let timestamp = rng.generate_u64(); clock.increment_for_testing(timestamp); - let creator_cap = CreatorCap { - id: object::new(ctx), - }; + let owner_cap = owner_cap::create(ctx); let mut scenario = sui::test_scenario::begin(@0x1); setup( - creator_cap, + &owner_cap, operator, domain_separator, minimum_rotation_delay, @@ -451,6 +450,7 @@ fun test_setup() { assert!(previous_signers_retention == previous_signers_retention_result); clock.destroy_for_testing(); + owner_cap.destroy_for_testing(); scenario.end(); } @@ -468,15 +468,13 @@ fun test_setup_remaining_bytes() { let timestamp = rng.generate_u64(); clock.increment_for_testing(timestamp); - let creator_cap = CreatorCap { - id: object::new(ctx), - }; + let owner_cap = owner_cap::create(ctx); let mut scenario = sui::test_scenario::begin(@0x1); let mut initial_signers_bytes = bcs::to_bytes(&initial_signers); initial_signers_bytes.push_back(0); setup( - creator_cap, + &owner_cap, operator, domain_separator, minimum_rotation_delay, @@ -526,6 +524,7 @@ fun test_setup_remaining_bytes() { assert!(previous_signers_retention == previous_signers_retention_result); clock.destroy_for_testing(); + owner_cap.destroy_for_testing(); scenario.end(); } @@ -1052,9 +1051,37 @@ fun test_send_message() { let gateway = dummy(ctx); gateway.send_message(message_ticket); - + utils::assert_event(); sui::test_utils::destroy(gateway); channel.destroy(); } + +#[test] +fun test_allow_function() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = dummy(ctx); + let owner_cap = owner_cap::create(ctx); + let version = 0; + let function_name = b"function_name".to_ascii_string(); + + self.allow_function(&owner_cap, version, function_name); + + sui::test_utils::destroy(self); + owner_cap.destroy_for_testing(); +} + +#[test] +fun test_disallow_function() { + let ctx = &mut sui::tx_context::dummy(); + let mut self = dummy(ctx); + let owner_cap = owner_cap::create(ctx); + let version = 0; + let function_name = b"approve_messages".to_ascii_string(); + + self.disallow_function(&owner_cap, version, function_name); + + sui::test_utils::destroy(self); + owner_cap.destroy_for_testing(); +} diff --git a/move/axelar_gateway/sources/types/owner_cap.move b/move/axelar_gateway/sources/types/owner_cap.move new file mode 100644 index 00000000..79b83a67 --- /dev/null +++ b/move/axelar_gateway/sources/types/owner_cap.move @@ -0,0 +1,23 @@ +module axelar_gateway::owner_cap; + +// ----- +// Types +// ----- +public struct OwnerCap has key, store { + id: UID, +} + +public(package) fun create(ctx: &mut TxContext): OwnerCap { + OwnerCap { + id: object::new(ctx), + } +} + +/// --------- +/// Test Only +/// --------- +#[test_only] +public(package) fun destroy_for_testing(self: OwnerCap) { + let OwnerCap { id } = self; + id.delete(); +} diff --git a/move/axelar_gateway/sources/versioned/gateway_v0.move b/move/axelar_gateway/sources/versioned/gateway_v0.move index 15193647..b9510bc6 100644 --- a/move/axelar_gateway/sources/versioned/gateway_v0.move +++ b/move/axelar_gateway/sources/versioned/gateway_v0.move @@ -55,7 +55,7 @@ public enum CommandType { // ----------------- // Package Functions // ----------------- -/// Init the module by giving a CreatorCap to the sender to allow a full +/// Init the module by giving a OwnerCap to the sender to allow a full /// `setup`. public(package) fun new( operator: address, @@ -221,6 +221,14 @@ public(package) fun send_message( ); } +public(package) fun allow_function(self: &mut Gateway_v0, version: u64, function_name: String) { + self.version_control.allow_function(version, function_name); +} + +public(package) fun disallow_function(self: &mut Gateway_v0, version: u64, function_name: String) { + self.version_control.disallow_function(version, function_name); +} + // ----------------- // Private Functions // ----------------- diff --git a/move/version_control/sources/version_control.move b/move/version_control/sources/version_control.move index a61acd19..b8aa71c3 100644 --- a/move/version_control/sources/version_control.move +++ b/move/version_control/sources/version_control.move @@ -1,4 +1,5 @@ -/// This module implements a custom version control scheme to maximize versioning customizability. +/// This module implements a custom version control scheme to maximize +/// versioning customizability. module version_control::version_control; use std::ascii::String; @@ -11,10 +12,19 @@ use sui::vec_set::{Self, VecSet}; const EFunctionNotSupported: vector = b"function is not supported in this version"; +#[error] +const EFunctionAlreadyAllowed: vector = + b"trying to allow a function already allowed on the specified version"; + +#[error] +const EFunctionAlreadyDisallowed: vector = + b"trying to disallow a function already disallowed on the specified version"; + // ----- // Types // ----- -/// The function names are stored as Strings. They are however input as vector for ease of instantiation. +/// The function names are stored as Strings. They are however input as +/// vector for ease of instantiation. public struct VersionControl has store, copy, drop { allowed_functions: vector>, } @@ -23,15 +33,18 @@ public struct VersionControl has store, copy, drop { // Public Functions // ---------------- -/// Create a new Version Control object by passing in the allowed_functions data. -/// You are supposed to pass a vector of the bytes of the functions that are allowed per version. For example: +/// Create a new Version Control object by passing in the allowed_functions +/// data. +/// You are supposed to pass a vector of the bytes of the functions that are +/// allowed per version. For example: /// ``` /// vector [ /// vector [ b"v0_function" ], /// vector [ b"v0_function", b"v1_function"], /// ] /// ``` -/// Would allow only `v0_function` to be called on version == 0, and both `v0_function` and `v1_function` to be called on version == 1. +/// Would allow only `v0_function` to be called on version == 0, and both +/// `v0_function` and `v1_function` to be called on version == 1. /// This is done to simplify the instantiation syntax of VersionControl. public fun new(allowed_functions: vector>): VersionControl { VersionControl { @@ -44,14 +57,16 @@ public fun new(allowed_functions: vector>): VersionControl { } /// This allowes for anyone to modify the raw data of allowed functions. -/// Do not pass a mutable reference of your VersionControl to anyone you do not trust because they can modify it. +/// Do not pass a mutable reference of your VersionControl to anyone you do not +/// trust because they can modify it. public fun allowed_functions( self: &mut VersionControl, ): &mut vector> { &mut self.allowed_functions } -/// If a new version does not need to deprecate any old functions, you can use this to add the newly supported functions. +/// If a new version does not need to deprecate any old functions, you can use +/// this to add the newly supported functions. public fun push_back( self: &mut VersionControl, function_names: vector, @@ -65,6 +80,30 @@ public fun push_back( ); } +public fun allow_function( + self: &mut VersionControl, + version: u64, + function_name: String, +) { + assert!( + !self.allowed_functions[version].contains(&function_name), + EFunctionAlreadyAllowed, + ); + self.allowed_functions[version].insert(function_name); +} + +public fun disallow_function( + self: &mut VersionControl, + version: u64, + function_name: String, +) { + assert!( + self.allowed_functions[version].contains(&function_name), + EFunctionAlreadyDisallowed, + ); + self.allowed_functions[version].remove(&function_name); +} + /// Call this at the begining of each version controlled function. For example /// ``` /// public fun do_something(data: &mut DataType) { @@ -79,6 +118,11 @@ public fun check(self: &VersionControl, version: u64, function: String) { ); } +/// Returns the latest valid index in allowed functions. +public fun latest_version(self: &VersionControl): u64 { + self.allowed_functions.length() - 1 +} + #[test] fun test_new() { let version_control = new(vector[ @@ -165,3 +209,58 @@ fun test_check_function_not_supported() { ]); version_control.check(0, b"function_name_2".to_ascii_string()); } + +#[test] +fun test_allow_function() { + let version = 0; + let function_name = b"function_name".to_ascii_string(); + let mut self = new(vector[vector[]]); + + self.allow_function(version, function_name); + + sui::test_utils::destroy(self); +} + +#[test] +fun test_disallow_function() { + let version = 0; + let function_name = b"function_name".to_ascii_string(); + let mut self = new(vector[vector[function_name]]); + + self.disallow_function(version, function_name); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EFunctionAlreadyAllowed)] +fun test_allow_function_already_allowed() { + let version = 0; + let function_name = b"function_name".to_ascii_string(); + let mut self = new(vector[vector[function_name]]); + + self.allow_function(version, function_name); + + sui::test_utils::destroy(self); +} + +#[test] +#[expected_failure(abort_code = EFunctionAlreadyDisallowed)] +fun test_disallow_function_already_disallowed() { + let version = 0; + let function_name = b"function_name".to_ascii_string(); + let mut self = new(vector[vector[]]); + + self.disallow_function(version, function_name); + + sui::test_utils::destroy(self); +} + +#[test] +fun test_latest_function() { + let self = new(vector[vector[]]); + + assert!(self.latest_version() == 0); + + sui::test_utils::destroy(self); +} diff --git a/scripts/limits.js b/scripts/limits.js index b2fb0bcf..48862b7c 100644 --- a/scripts/limits.js +++ b/scripts/limits.js @@ -224,7 +224,7 @@ async function prepare(network, keypairs, domainSeparator) { let result = await publishPackage(client, deployer, 'axelar_gateway'); packageId = result.packageId; const creatorCap = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${packageId}::gateway::CreatorCap`, + (change) => change.objectType === `${packageId}::gateway::OwnerCap`, ).objectId; result = await publishPackage(client, deployer, 'relayer_discovery'); const discoveryPackageId = result.packageId; diff --git a/test/axelar-gateway.js b/test/axelar-gateway.js index 5c3e04cb..645be93a 100644 --- a/test/axelar-gateway.js +++ b/test/axelar-gateway.js @@ -77,7 +77,7 @@ describe('Axelar Gateway', () => { let result = await publishPackage(client, deployer, 'axelar_gateway'); packageId = result.packageId; const creatorCap = result.publishTxn.objectChanges.find( - (change) => change.objectType === `${packageId}::gateway::CreatorCap`, + (change) => change.objectType === `${packageId}::owner_cap::OwnerCap`, ).objectId; result = await publishPackage(client, deployer, 'relayer_discovery'); const discoveryPackageId = result.packageId; diff --git a/test/its.js b/test/its.js index 02d0c57b..21cc079b 100644 --- a/test/its.js +++ b/test/its.js @@ -126,7 +126,7 @@ describe('ITS', () => { `${deployments.relayer_discovery.packageId}::discovery::RelayerDiscovery`, ), gasService: findObjectId(deployments.gas_service.publishTxn, `${deployments.gas_service.packageId}::gas_service::GasService`), - creatorCap: findObjectId(deployments.axelar_gateway.publishTxn, 'CreatorCap'), + creatorCap: findObjectId(deployments.axelar_gateway.publishTxn, 'OwnerCap'), itsOwnerCap: findObjectId(deployments.its.publishTxn, `${deployments.its.packageId}::owner_cap::OwnerCap`), }; // Mint some coins for tests diff --git a/test/squid.js b/test/squid.js index fa234023..61b45ab7 100644 --- a/test/squid.js +++ b/test/squid.js @@ -392,7 +392,7 @@ describe('Squid', () => { `${deployments.relayer_discovery.packageId}::discovery::RelayerDiscovery`, ), gasService: findObjectId(deployments.gas_service.publishTxn, `${deployments.gas_service.packageId}::gas_service::GasService`), - creatorCap: findObjectId(deployments.axelar_gateway.publishTxn, 'CreatorCap'), + creatorCap: findObjectId(deployments.axelar_gateway.publishTxn, 'OwnerCap'), itsOwnerCap: findObjectId(deployments.its.publishTxn, `${deployments.its.packageId}::owner_cap::OwnerCap`), gateway: findObjectId(deployments.its.publishTxn, `${deployments.axelar_gateway.packageId}::gateway::Gateway`), }; diff --git a/test/testdata/interface_axelar_gateway_gateway.json b/test/testdata/interface_axelar_gateway_gateway.json index c63d0cc0..f654c50b 100644 --- a/test/testdata/interface_axelar_gateway_gateway.json +++ b/test/testdata/interface_axelar_gateway_gateway.json @@ -15,19 +15,6 @@ "type": "Versioned" } ] - }, - "CreatorCap": { - "name": "CreatorCap", - "abilities": [ - "store", - "key" - ], - "fields": [ - { - "name": "id", - "type": "UID" - } - ] } }, "publicFunctions": { diff --git a/test/testdata/interface_axelar_gateway_owner_cap.json b/test/testdata/interface_axelar_gateway_owner_cap.json new file mode 100644 index 00000000..4c71365c --- /dev/null +++ b/test/testdata/interface_axelar_gateway_owner_cap.json @@ -0,0 +1,18 @@ +{ + "structs": { + "OwnerCap": { + "name": "OwnerCap", + "abilities": [ + "store", + "key" + ], + "fields": [ + { + "name": "id", + "type": "UID" + } + ] + } + }, + "publicFunctions": {} +} diff --git a/test/testdata/interface_version_control_version_control.json b/test/testdata/interface_version_control_version_control.json index 527dcd21..249b8edf 100644 --- a/test/testdata/interface_version_control_version_control.json +++ b/test/testdata/interface_version_control_version_control.json @@ -31,6 +31,14 @@ "self#0#0": "&mut VersionControl" }, "returnType": "&mut vector>" + }, + "latest_version": { + "name": "latest_version", + "visibility": "public", + "params": { + "self#0#0": "&VersionControl" + }, + "returnType": "u64" } } }