Skip to content

Commit d009220

Browse files
feat(sui): custom token linking v1 register coins and migrate legacy coin registrations (axelarnetwork#856)
Co-authored-by: Blockchain Guy <[email protected]>
1 parent d0b9adc commit d009220

File tree

12 files changed

+1342
-20
lines changed

12 files changed

+1342
-20
lines changed

common/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ const isString = (arg) => {
115115
return typeof arg === 'string';
116116
};
117117

118+
const isNonArrayObject = (arg) => {
119+
if (!arg) return false;
120+
return typeof arg === 'object' && Array.isArray(arg) === false;
121+
};
122+
118123
const isNonEmptyString = (arg) => {
119124
return isString(arg) && arg !== '';
120125
};
@@ -426,6 +431,7 @@ function isValidSvmAddressFormat(address) {
426431
const validationFunctions = {
427432
isNonEmptyString,
428433
isNumber,
434+
isNonArrayObject,
429435
isValidNumber,
430436
isValidDecimal,
431437
isNumberArray,
@@ -805,6 +811,7 @@ module.exports = {
805811
isStringArray,
806812
isStringLowercase,
807813
isNumber,
814+
isNonArrayObject,
808815
isValidNumber,
809816
isValidDecimal,
810817
isNumberArray,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Sui ITS 0.0.0-snapshot.3e3ec75
2+
3+
| | **Owner** |
4+
| -------------- | -------------------------------------- |
5+
| **Created By** | @drewstaylor <[email protected]> |
6+
| **Deployment** | @blockchainguyy <[email protected]> |
7+
8+
| **Network** | **Deployment Status** | **Date** |
9+
| -------------------- | --------------------- | ---------- |
10+
| **Devnet Amplifier** | TBD | TBD |
11+
| **Stagenet** | TBD | TBD |
12+
| **Testnet** | TBD | TBD |
13+
| **Mainnet** | TBD | TBD |
14+
15+
[Release](https://www.npmjs.com/package/@axelar-network/axelar-cgp-sui/v/0.0.0-snapshot.3e3ec75)
16+
17+
## Deployment
18+
19+
```bash
20+
# Clone latest main and update deps
21+
npm ci && npm run build
22+
```
23+
24+
Create a `.env` config. Use `all` for `CHAINS` to run the cmd for every EVM chain, or set a specific chain. On `devnet-amplifier` chain name will be set to `sui-2`.
25+
26+
```yaml
27+
PRIVATE_KEY=<sui-deployer-key>
28+
PRIVATE_KEY_TYPE="mnemonic" # Optional
29+
SIGNATURE_SCHEME=secp256k1
30+
ENV=<devnet-amplifier|stagenet|testnet|mainnet>
31+
CHAIN=sui
32+
```
33+
34+
### ITS Contract Admin & Roles
35+
36+
Many of the below commands require either Owner or Operator privileges on the ITS contract.
37+
38+
#### OwnerCap
39+
40+
Some commands require a wallet that owns the contract's admin capability (`OwnerCap`). By default, this will be the address that deployed the ITS v0 contract.
41+
42+
The commands requiring this capability are:
43+
* [Upgrading the ITS move contract][]
44+
* [Enabling the Upgrade][]
45+
* [Disabling the Legacy Contract][]
46+
47+
#### OperatorCap
48+
49+
Migrating legacy coin metadata requires a `OperatorCap` capability. This is any address added as an operator for the effected coin. By default, the address that deployed and initialized the ITS v0 contract possesses a `OperatorCap`.
50+
51+
The commands requiring this capability are:
52+
* [Migrating Legacy Tokens][]
53+
54+
### Fetching Legacy Tokens
55+
56+
To fix the issue of some tokens not displaying correctly in wallets, we need to fetch the effected tokens. This list can be fetched before, or after the upgrade.
57+
58+
```bash
59+
# Save legacy tokens for later migration
60+
ts-node sui/tokens legacy-coins
61+
```
62+
63+
### Upgrading the ITS move contract ###
64+
65+
1. Update the release dependency in `package.json`
66+
67+
```diff
68+
- "@axelar-network/axelar-cgp-sui": "1.1.3",
69+
+ "@axelar-network/axelar-cgp-sui": "0.0.0-snapshot.3e3ec75",
70+
```
71+
72+
and run
73+
```bash
74+
npm i
75+
```
76+
77+
2. Update the move contracts locally
78+
79+
```bash
80+
node sui/deploy-contract sync
81+
```
82+
3. Do the upgrade tx
83+
84+
```bash
85+
# Upgrade InterchainTokenService v0 -> v1
86+
ts-node sui/deploy-contract upgrade InterchainTokenService any_upgrade
87+
```
88+
89+
### Enabling the Upgrade ###
90+
91+
Migrate version control state to enable the upgrade.
92+
93+
```bash
94+
ts-node sui/deploy-contract migrate InterchainTokenService
95+
```
96+
97+
## Checklist
98+
99+
The following post-upgrade tasks should be performed after the rollout
100+
101+
- [ ] Disable the legacy ITS contract
102+
- [ ] Verify Version Control state (v0 and v1)
103+
- [ ] Re-deploy Example contract
104+
- [ ] Fetch metadata of legacy coins (if not already fetched)
105+
- [ ] Migrate metadata of legacy coins
106+
- [ ] Test ITS token deployment
107+
108+
### Disabling the Legacy Contract ###
109+
110+
```bash
111+
# Disallow all functions in the legacy ITS package
112+
ts-node sui/contract pause --functions "all" --version "0" InterchainTokenService
113+
```
114+
115+
### Verify Version Control State
116+
117+
1. All functions should be enabled on v1
118+
119+
```bash
120+
ts-node sui/its check-version-control 1
121+
```
122+
123+
2. Only `allow_function` and `disallow_function` should be enabled on v0
124+
125+
```bash
126+
ts-node sui/its check-version-control 0
127+
```
128+
129+
### Migrating Legacy Tokens ###
130+
131+
```bash
132+
# Migrate legacy tokens in batches of 10 coins per tx
133+
ts-node sui/its migrate-coin-metadata-all --batch 10
134+
# Or
135+
# Migrate legacy tokens in batches of 10, with verbose logging
136+
ts-node sui/its migrate-coin-metadata-all --batch 10 --logging 10
137+
```
138+
139+
#### Test ITS token deployment
140+
141+
Test interchain token deployment and transfer with EVM(consensus and amplifier) chains and Stellar.

sui/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,15 @@ policy should be one of the following:
229229

230230
Provide `--txFilePath` with `--offline` to generate tx data file for offline signing.
231231

232+
### Migrating Post-Upgrade
233+
234+
After upgrading a package, state migrations (e.g. for [versioned](https://docs.sui.io/references/framework/sui/versioned) packages) can be called using the `migrate` command.
235+
236+
237+
```bash
238+
ts-node sui/deploy-contract.js migrate AxelarGateway
239+
```
240+
232241
### Multisig Operations
233242

234243
To create a Multisig, follow the documentation [here](https://docs.sui.io/guides/developer/cryptography/multisig).
@@ -432,6 +441,84 @@ Remove trusted chains
432441
ts-node sui/its.js remove-trusted-chains <sourceChain> <sourceChain2> ...
433442
```
434443

444+
## Registering Coins
445+
446+
### Register Coin from Info (symbol, name and decimals)
447+
448+
```bash
449+
ts-node sui/its.js register-coin-from-info <symbol> <name> <decimals>
450+
```
451+
452+
### Register Coin from Metadata
453+
454+
(see: [sui::coin::CoinMetadata](https://docs.sui.io/references/sui-api/sui-graphql/reference/types/objects/coin-metadata))
455+
456+
```bash
457+
ts-node sui/its.js register-coin-from-metadata <symbol> <name> <decimals>
458+
```
459+
460+
### Register Custom Coin
461+
462+
If a `channel` id is present in the `options` array (e.g. `--channel <channel>`) it will be used, otherwise a new `channel` will be created and transferred to the sender. A `salt` for the registration transaction will automatically be created.
463+
464+
```bash
465+
ts-node sui/its.js register-custom-coin <symbol> <name> <decimals>
466+
```
467+
468+
## Migrating Legacy Coin Registrations
469+
470+
### Migrate Coin Metadata
471+
472+
_Added in v1 to fix coins that were not displaying correctly in wallet softwares. Only callable for coins with metadata owned by ITS. Will [publicly freeze](https://docs.sui.io/references/framework/sui/transfer#sui_transfer_public_freeze_object) a coin's metadata, making it a publicly shared object._
473+
474+
```bash
475+
ts-node sui/its.js migrate-coin-metadata <symbol>
476+
```
477+
478+
## Coin Linking
479+
480+
### Give Unlinked Coin
481+
482+
Deploys a coin on Sui, registers it as custom coin and gives its treasury capability to ITS. Treasury capability will be reclaimable if the `--treasuryCapReclaimer` flag is passed to the command options.
483+
484+
```bash
485+
ts-node sui/its give-unlinked-coin [options] <symbol> <name> <decimals>
486+
```
487+
488+
### Remove Unlinked Coin
489+
490+
Removes a coin from ITS and returns its TreasuryCap to the caller. Caller must own the coin's TreasuryCapReclaimer.
491+
492+
```bash
493+
ts-node sui/its remove-unlinked-coin [options] <symbol>
494+
```
495+
496+
### Link Coin
497+
498+
Deploys a source coin and links it with a destination chain coin. If a `channel` id is present in the `options` array (e.g. `--channel <channel>`) it will be used, otherwise a new `channel` will be created and transferred to the sender. A `salt` for the coin registration and linking transactions will automatically be created.
499+
500+
```bash
501+
ts-node sui/its link-coin <symbol> <name> <decimals> <destinationChain> <destinationAddress>
502+
```
503+
504+
## Treasury Management
505+
506+
### Remove Treasury Cap
507+
508+
Transfers the coin's `TreasuryCap` to the coin deployer and reclaims mint/burn permission from ITS.
509+
510+
```bash
511+
ts-node sui/its remove-treasury-cap [options] <symbol>
512+
```
513+
514+
### Restore Treasury Cap
515+
516+
Restore a coin's TreasuryCap to ITS after calling remove-treasury-cap, giving mint/burn permission back to ITS.
517+
518+
```bash
519+
ts-node sui/its restore-treasury-cap [options] <symbol>
520+
```
521+
435522
## Sui Contract Verification
436523

437524
This script generates a `verification` folder inside the `move` directory, which contains ZIP files for each contract to be used for verification. Before zipping, a `deps` subdirectory is added to each contract, and the local dependency paths in the `Move.toml` file are updated to reference the `deps` folder.

sui/contract.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,10 @@ async function pause(keypair, client, chain, args, options) {
118118

119119
let allowedFunctions = allowedFunctionsArray[version];
120120

121-
// Do not dissalow `allow_function` because that locks the gateway forever.
122-
if (Number(version) === allowedFunctionsArray.length - 1) {
123-
allowedFunctions = allowedFunctions.filter(
124-
(allowedFunction) => allowedFunction !== 'allow_function' && allowedFunction !== 'disallow_function',
125-
);
126-
}
121+
// Do not disable `allow_function` because that locks the contract forever.
122+
allowedFunctions = allowedFunctions.filter((allowedFunction) => {
123+
return allowedFunction !== 'allow_function' && allowedFunction !== 'disallow_function';
124+
});
127125

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

sui/deploy-contract.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ const {
2525
getStructs,
2626
restrictUpgradePolicy,
2727
broadcastRestrictedUpgradePolicy,
28+
broadcastFromTxBuilder,
2829
} = require('./utils');
30+
const GatewayCli = require('./gateway');
2931

3032
/**
3133
* Move Package Directories
@@ -442,6 +444,47 @@ async function upgrade(keypair, client, supportedPackage, policy, config, chain,
442444
}
443445
}
444446

447+
async function migrate(keypair, client, supportedPackage, config, chain, options) {
448+
const { packageName } = supportedPackage;
449+
450+
validateParameters({
451+
// Contract
452+
isNonArrayObject: { contractEntry: chain.contracts[packageName] },
453+
isNonEmptyString: { contractAddress: chain.contracts[packageName].address },
454+
// OwnerCap
455+
isNonArrayObject: { ownerEntry: chain.contracts[packageName].objects },
456+
isNonEmptyString: { ownerAddress: chain.contracts[packageName].objects.OwnerCap },
457+
});
458+
const contractConfig = chain.contracts[packageName];
459+
const ownerCap = contractConfig.objects.OwnerCap;
460+
461+
const builder = new TxBuilder(client);
462+
463+
switch (packageName) {
464+
case 'AxelarGateway': {
465+
const result = await GatewayCli.migrate(keypair, client, config, chain, contractConfig, null, options);
466+
return await broadcast(client, keypair, result.tx, result.message, options);
467+
}
468+
case 'InterchainTokenService': {
469+
const InterchainTokenService = contractConfig.objects.InterchainTokenService;
470+
471+
if (typeof InterchainTokenService !== 'string') throw new Error(`Cannot find object of specified contract: ${packageName}`);
472+
473+
await builder.moveCall({
474+
target: `${contractConfig.address}::interchain_token_service::migrate`,
475+
arguments: [InterchainTokenService, ownerCap],
476+
});
477+
478+
break;
479+
}
480+
default: {
481+
throw new Error(`Post-upgrade migration not supported for ${packageName}`);
482+
}
483+
}
484+
485+
if (packageName !== 'AxelarGateway') await broadcastFromTxBuilder(builder, keypair, `Migrate Package ${packageName}`, options);
486+
}
487+
445488
async function syncPackages(keypair, client, config, chain, options) {
446489
// Remove the move directory and its contents if it exists
447490
fs.rmSync(moveDir, { recursive: true, force: true });
@@ -528,6 +571,7 @@ if (require.main === module) {
528571
// 2nd level commands
529572
const deployCmd = new Command('deploy').description('Deploy a Sui package');
530573
const upgradeCmd = new Command('upgrade').description('Upgrade a Sui package');
574+
const migrateCmd = new Command('migrate').description('Migrate a Sui package after upgrading');
531575

532576
// 3rd level commands for `deploy`
533577
const deployContractCmds = supportedPackages.map((supportedPackage) => {
@@ -557,21 +601,42 @@ if (require.main === module) {
557601
});
558602
});
559603

604+
// 3rd level commands for `migrate`
605+
const migrateContractCmds = supportedPackages.map((supportedPackage) => {
606+
const { packageName } = supportedPackage;
607+
return new Command(packageName)
608+
.description(`Migrate ${packageName} contract after upgrade`)
609+
.command(`${packageName}`)
610+
.addOption(new Option('--migrate-data <migrateData>', 'bcs encoded data to pass to the migrate function'))
611+
.addOption(new Option('--sender <sender>', 'transaction sender'))
612+
.addOption(new Option('--digest <digest>', 'digest hash for upgrade'))
613+
.addOption(new Option('--offline', 'store tx block for sign'))
614+
.addOption(new Option('--txFilePath <file>', 'unsigned transaction will be stored'))
615+
.action((options) => {
616+
mainProcessor([supportedPackage], options, migrate);
617+
});
618+
});
619+
560620
const syncCmd = new Command('sync').description('Sync local Move packages with deployed addresses').action((options) => {
561621
mainProcessor([], options, syncPackages);
562622
});
563623

564624
// Add 3rd level commands to 2nd level command `upgrade`
565625
upgradeContractCmds.forEach((cmd) => upgradeCmd.addCommand(cmd));
566626

627+
// Add 3rd level commands to 2nd level command `migrate`
628+
migrateContractCmds.forEach((cmd) => migrateCmd.addCommand(cmd));
629+
567630
// Add base options to all 2nd and 3rd level commands
568631
addOptionsToCommands(deployCmd, addBaseOptions);
569632
addOptionsToCommands(upgradeCmd, addBaseOptions);
633+
addOptionsToCommands(migrateCmd, addBaseOptions);
570634
addBaseOptions(syncCmd);
571635

572636
// Add 2nd level commands to 1st level command
573637
program.addCommand(deployCmd);
574638
program.addCommand(upgradeCmd);
639+
program.addCommand(migrateCmd);
575640
program.addCommand(syncCmd);
576641

577642
program.parse();

0 commit comments

Comments
 (0)