Skip to content

Commit

Permalink
feat(sui)!: add script option to pause sui contracts (#519)
Browse files Browse the repository at this point in the history
Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
Foivos and milapsheth authored Feb 7, 2025
1 parent f2870e3 commit 881e728
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 86 deletions.
26 changes: 25 additions & 1 deletion .github/workflows/test-sui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ jobs:
run: node sui/gas-service.js collectGas --amount 0.1

###### Command: Gateway ######
- name: Gateway Pause
run: node sui/contract.js pause AxelarGateway -y

- name: Gateway Unpause
run: node sui/contract.js unpause AxelarGateway -y

- name: Gateway Pause All
run: node sui/contract.js pause AxelarGateway --functions all -y

- name: Gateway Unpause
run: node sui/contract.js unpause AxelarGateway -y

- name: Gateway Approve
run: node sui/gateway.js approve --proof wallet ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-2 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x6ce0d81b412abca2770eddb1549c9fcff721889c3aab1203dc93866db22ecc4b 0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432

Expand All @@ -190,8 +202,20 @@ jobs:
node sui/gateway.js approve --proof wallet ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-3 0x4F4495243837681061C4743b74B3eEdf548D56A5 $channel_id 0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432
node sui/gmp.js execute ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-3 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234
###### Command: ITS Example ######
###### Command: ITS ######
- name: ITS Pause
run: node sui/contract.js pause InterchainTokenService -y

- name: ITS Unpause
run: node sui/contract.js unpause InterchainTokenService -y

- name: ITS Pause
run: node sui/contract.js pause InterchainTokenService --functions all -y

- name: ITS Unpause
run: node sui/contract.js unpause InterchainTokenService -y

###### Command: ITS Example ######
- name: Prepare ITS Example Parameters
run: |
echo "sourceChain=Ethereum" >> $GITHUB_ENV
Expand Down
260 changes: 260 additions & 0 deletions sui/contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
const { Command, Option } = require('commander');
const { TxBuilder } = require('@axelar-network/axelar-cgp-sui');
const { loadConfig, saveConfig, getChainConfig, printInfo } = require('../common/utils');
const {
addBaseOptions,
addOptionsToCommands,
getWallet,
printWalletInfo,
broadcastFromTxBuilder,
getAllowedFunctions,
} = require('./utils');

const SPECIAL_PAUSE_FUNCTION_TAGS = {
ALL: 'all', // All EVM chains that have InterchainTokenService deployed
DEFAULT: 'default',
};

const SPECIAL_UNPAUSE_FUNCTION_TAGS = {
DISALLOWED: 'disallowed', // All EVM chains that have InterchainTokenService deployed
DEFAULT: 'default',
};

const CONTRACT_INFO = {
AxelarGateway: {
singletonName: 'Gateway',
moduleName: 'gateway',
defaultFunctions: {
versions: [0, 0],
functionNames: ['approve_messages', 'rotate_signers'],
},
},
InterchainTokenService: {
singletonName: 'InterchainTokenService',
moduleName: 'interchain_token_service',
defaultFunctions: {
versions: [0, 0, 0, 0, 0, 0, 0, 0, 0],
functionNames: [
'register_coin',
'deploy_remote_interchain_token',
'send_interchain_transfer',
'receive_interchain_transfer',
'receive_interchain_transfer_with_data',
'receive_deploy_interchain_token',
'mint_as_distributor',
'mint_to_as_distributor',
'burn_as_distributor',
],
},
},
};

function getVariablesForPackage(chain, packageName) {
const contractConfig = chain.contracts[packageName];
const info = CONTRACT_INFO[packageName];
const defaultFunctions = info.defaultFunctions;
const version = Math.max(...Object.keys(contractConfig.versions).map((version) => Number(version)));
defaultFunctions.versions.fill(version);
return {
packageId: contractConfig.address,
singletonId: contractConfig.objects[info.singletonName],
versionedId: contractConfig.objects[info.singletonName + 'v0'],
ownerCapId: contractConfig.objects.OwnerCap,
moduleName: info.moduleName,
defaultFunctions: info.defaultFunctions,
contract: contractConfig,
};
}

async function allowFunctions(keypair, client, packageId, moduleName, singletonId, ownerCapId, versions, functionNames, options) {
if (versions.length !== functionNames.length) throw new Error('Versions and Function Names must have a matching length');

const builder = new TxBuilder(client);

for (const i in versions) {
await builder.moveCall({
target: `${packageId}::${moduleName}::allow_function`,
arguments: [singletonId, ownerCapId, versions[i], functionNames[i]],
});
}

await broadcastFromTxBuilder(builder, keypair, 'Allow Functions', options);
}

async function disallowFunctions(keypair, client, packageId, moduleName, singletonId, ownerCapId, versions, functionNames, options) {
if (versions.length !== functionNames.length) throw new Error('Versions and Function Names must have a matching length');

const builder = new TxBuilder(client);

for (const i in versions) {
await builder.moveCall({
target: `${packageId}::${moduleName}::disallow_function`,
arguments: [singletonId, ownerCapId, versions[i], functionNames[i]],
});
}

await broadcastFromTxBuilder(builder, keypair, 'Disallow Functions', options);
}

async function pause(keypair, client, chain, args, options) {
const [packageName] = args;
const functions = options.functions;

const { packageId, singletonId, versionedId, ownerCapId, moduleName, defaultFunctions, contract } = getVariablesForPackage(
chain,
packageName,
);

let versionsArg = [];
let allowedFunctionsArg = [];

if (functions === SPECIAL_PAUSE_FUNCTION_TAGS.ALL) {
const allowedFunctionsArray = await getAllowedFunctions(client, versionedId);

for (const version in allowedFunctionsArray) {
if (options.version !== 'all' && options.version !== version) {
continue;
}

let allowedFunctions = allowedFunctionsArray[version];

// Do not dissalow `allow_function` because that locks the gateway forever.
if (Number(version) === allowedFunctionsArray.length - 1) {
allowedFunctions = allowedFunctions.filter(
(allowedFunction) => allowedFunction !== 'allow_function' && allowedFunction !== 'disallow_function',
);
}

printInfo(`Functions that will be disallowed for version ${version}`, allowedFunctions);

versionsArg = versionsArg.concat(new Array(allowedFunctions.length).fill(Number(version)));
allowedFunctionsArg = allowedFunctionsArg.concat(allowedFunctions);
}
} else if (functions === SPECIAL_PAUSE_FUNCTION_TAGS.DEFAULT) {
versionsArg = defaultFunctions.versions;
allowedFunctionsArg = defaultFunctions.functionNames;
} else if (options.version !== 'all') {
allowedFunctionsArg = functions.split(',');
versionsArg = allowedFunctionsArg.map(() => Number(options.version));
} else {
throw new Error('Need to specify a version if providing specific functions.');
}

if (!contract.disallowedFunctions) {
contract.disallowedFunctions = {
versions: [],
functionNames: [],
};
}

contract.disallowedFunctions.versions = contract.disallowedFunctions.versions.concat(versionsArg);
contract.disallowedFunctions.functionNames = contract.disallowedFunctions.functionNames.concat(allowedFunctionsArg);

return await disallowFunctions(
keypair,
client,
packageId,
moduleName,
singletonId,
ownerCapId,
versionsArg,
allowedFunctionsArg,
options,
);
}

async function unpause(keypair, client, chain, args, options) {
const [packageName] = args;
const functions = options.functions;
const { packageId, singletonId, ownerCapId, moduleName, defaultFunctions, contract } = getVariablesForPackage(chain, packageName);

let versionsArg = [];
let allowedFunctionsArg = [];

if (functions === SPECIAL_UNPAUSE_FUNCTION_TAGS.DISALLOWED) {
versionsArg = contract.disallowedFunctions.versions.slice();
allowedFunctionsArg = contract.disallowedFunctions.functionNames.slice();
} else if (functions === SPECIAL_UNPAUSE_FUNCTION_TAGS.DEFAULT) {
versionsArg = defaultFunctions.versions;
allowedFunctionsArg = defaultFunctions.functionNames;
} else if (options.version !== 'all') {
allowedFunctionsArg = functions.split(',');
versionsArg = allowedFunctionsArg.map(() => Number(options.version));
} else {
throw new Error('Need to specify a version if providing specific functions.');
}

if (contract.disallowedFunctions) {
for (let i = contract.disallowedFunctions.versions.length - 1; i >= 0; i--) {
const version = contract.disallowedFunctions.versions[i];
const functionName = contract.disallowedFunctions.functionNames[i];

for (let j = 0; j < versionsArg.length; j++) {
if (version === versionsArg[j] && functionName === allowedFunctionsArg[j]) {
contract.disallowedFunctions.versions.splice(i, 1);
contract.disallowedFunctions.functionNames.splice(i, 1);
break;
}
}
}
}

return await allowFunctions(keypair, client, packageId, moduleName, singletonId, ownerCapId, versionsArg, allowedFunctionsArg, options);
}

async function processCommand(command, chain, args, options) {
const [keypair, client] = getWallet(chain, options);

await printWalletInfo(keypair, client, chain, options);

await command(keypair, client, chain, args, options);
}

async function mainProcessor(command, options, args, processor) {
const config = loadConfig(options.env);
const chain = getChainConfig(config, options.chainName);
await processor(command, chain, args, options);
saveConfig(config, options.env);
}

if (require.main === module) {
const program = new Command();
program.name('Pause').description('SUI Pause scripts');

const pauseProgram = new Command()
.name('pause')
.description('Pause')
.command('pause <package>')
.addOption(
new Option(
'--functions <functions>',
'The functions to allow. Use use "default" for the default functions, "all" for all functions except the most recent "allow_function" and a comma separated list for custom pausing.',
).default('default'),
)
.addOption(new Option('--version, <version>', 'The version to pause. Use all to pause all versions').default('all'))
.action((packageName, options) => {
mainProcessor(pause, options, [packageName], processCommand);
});

const unpauseProgram = new Command()
.name('unpause')
.description('Unpause')
.command('unpause <package>')
.addOption(
new Option(
'--functions, <functions>',
'The functions to pause. Use "disallowed" for previously disallowed functions, "default" for the default functions and a comma separated list for custom pausing.',
).default('disallowed'),
)
.addOption(new Option('--version, <version>', 'The version to pause. Use all to pause all versions').default('all'))
.action((packageName, options) => {
mainProcessor(unpause, options, [packageName], processCommand);
});

program.addCommand(pauseProgram);
program.addCommand(unpauseProgram);

addOptionsToCommands(program, addBaseOptions, { offline: true });

program.parse();
}
76 changes: 0 additions & 76 deletions sui/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,68 +282,6 @@ async function rotate(keypair, client, config, chain, contractConfig, args, opti
};
}

async function allowFunctions(keypair, client, config, chain, contractConfig, args, options) {
const packageId = contractConfig.address;

const [versionsArg, functionNamesArg] = args;

const versions = versionsArg.split(',');
const functionNames = functionNamesArg.split(',');

if (versions.length !== functionNames.length) throw new Error('Versions and Function Names must have a matching length');

const tx = new Transaction();
console.log(contractConfig.objects);

for (const i in versions) {
tx.moveCall({
target: `${packageId}::gateway::allow_function`,
arguments: [
tx.object(contractConfig.objects.Gateway),
tx.object(contractConfig.objects.OwnerCap),
tx.pure.u64(versions[i]),
tx.pure.string(functionNames[i]),
],
});
}

return {
tx,
message: 'Allow Functions',
};
}

async function disallowFunctions(keypair, client, config, chain, contractConfig, args, options) {
const packageId = contractConfig.address;

const [versionsArg, functionNamesArg] = args;

const versions = versionsArg.split(',');
const functionNames = functionNamesArg.split(',');

if (versions.length !== functionNames.length) throw new Error('Versions and Function Names must have a matching length');

const tx = new Transaction();
console.log(contractConfig.objects);

for (const i in versions) {
tx.moveCall({
target: `${packageId}::gateway::disallow_function`,
arguments: [
tx.object(contractConfig.objects.Gateway),
tx.object(contractConfig.objects.OwnerCap),
tx.pure.u64(versions[i]),
tx.pure.string(functionNames[i]),
],
});
}

return {
tx,
message: 'Disallow Functions',
};
}

async function checkVersionControl(version, options) {
const config = loadConfig(options.env);

Expand Down Expand Up @@ -582,20 +520,6 @@ if (require.main === module) {
mainProcessor(callContract, [destinationChain, destinationAddress, payload], options);
});

program
.command('allow-functions <versions> <functionNames>')
.description('Allow certain funcitons on the gateway')
.action((versions, functionNames, options) => {
mainProcessor(allowFunctions, [versions, functionNames], options);
});

program
.command('disallow-functions <versions> <functionNames>')
.description('Allow certain funcitons on the gateway')
.action((versions, functionNames, options) => {
mainProcessor(disallowFunctions, [versions, functionNames], options);
});

program
.command('check-version-control <version>')
.description('Check if version control works on a certain version')
Expand Down
Loading

0 comments on commit 881e728

Please sign in to comment.