diff --git a/.gitignore b/.gitignore index 1ede6657..c7bc0590 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,6 @@ sui/move # VSCode .vscode .DS_Store + +# Jetbrains +.idea diff --git a/package.json b/package.json index 7ada3c84..92303be6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@axelar-network/axelar-contract-deployments", + "type": "commonjs", "version": "1.4.0", "description": "Axelar contract deployment scripts", "main": "index.js", diff --git a/stellar/README.md b/stellar/README.md index c03f2f19..97ee6671 100644 --- a/stellar/README.md +++ b/stellar/README.md @@ -88,9 +88,9 @@ node stellar/deploy-contract.js deploy axelar_gas_service --chain-name --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/interchain_token.optimized.wasm -```bash node stellar/deploy-contract.js deploy interchain_token_service --chain-name --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/interchain_token_service.optimized.wasm ``` @@ -100,6 +100,22 @@ node stellar/deploy-contract.js deploy interchain_token_service --chain-name --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/example.optimized.wasm ``` +### Contract upgrades + +To facilitate contract upgrades, the `upgrader` contract needs to be deployed first. + +```bash +node stellar/deploy-contract.js deploy upgrader --chain-name --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/upgrader.optimized.wasm +``` + +After the `upgrader` is deployed, any other instantiated contract can be upgraded by calling the `upgrade` function + +```bash +node stellar/deploy-contract.js upgrade --chain-name --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/.optimized.wasm --new-version --migration-data +``` + +where `` is the name of the contract to be upgraded and `--wasm-path` points to the upgraded bytecode. As a sanity check, `` must match the version number defined by the provided bytecode, so upgrading to the wrong version can be prevented. `` is the json encoded data that will be passed to the contract's `migrate` function. If the flag is not provided, the default value `()` will be used, meaning that the migration data is of type `void`. The easiest way to generate the json data for complex types is to instantiate the rust type the contract expects and then use `serde_json::to_string` to convert it to json. + ## Generate bindings Generate TypeScript bindings for the contract @@ -166,14 +182,6 @@ node stellar/gateway.js rotate --new-nonce test --signers wallet node stellar/gateway.js rotate --new-nonce test2 --current-nonce test --signers wallet ``` -#### Upgrade Gateway - -To upgrade the gateway, run the following command: - -```bash -node stellar/deploy-contract.js upgrade axelar_gateway --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_gateway.optimized.wasm -``` - ### Interchain Token Service _Note_: Stellar ITS runs only in Hub mode. P2P connections are not supported. Therefore, rather than setting trusted ITS addresses, we set trusted chains (chains which are also registered with ITS Hub). The ITS Hub chain (axelar) itself is not a valid source/destination for direct ITS messages and so shouldn't be set as a trusted chain. All ITS messages must be sent to and received from the ITS Hub. diff --git a/stellar/deploy-contract.js b/stellar/deploy-contract.js index 23b23f59..8134da0e 100644 --- a/stellar/deploy-contract.js +++ b/stellar/deploy-contract.js @@ -1,9 +1,9 @@ 'use strict'; -const { Address, nativeToScVal, scValToNative, Operation, StrKey } = require('@stellar/stellar-sdk'); +const { Address, nativeToScVal, scValToNative, Operation, StrKey, xdr, authorizeInvocation, rpc } = require('@stellar/stellar-sdk'); const { Command, Option } = require('commander'); const { loadConfig, printInfo, saveConfig } = require('../evm/utils'); -const { getWallet, broadcast, serializeValue, addBaseOptions } = require('./utils'); +const { getWallet, broadcast, serializeValue, addBaseOptions, getNetworkPassphrase, createAuthorizedFunc } = require('./utils'); const { getDomainSeparator, getChainConfig } = require('../common'); const { prompt, validateParameters } = require('../common/utils'); const { weightedSignersToScVal } = require('./type-utils'); @@ -74,6 +74,10 @@ async function getInitializeArgs(config, chain, contractName, wallet, options) { return { owner, gasCollector }; } + case 'upgrader': { + return {}; + } + case 'example': { const gatewayAddress = nativeToScVal(Address.fromString(chain?.contracts?.axelar_gateway?.address), { type: 'address' }); const gasServiceAddress = nativeToScVal(Address.fromString(chain?.contracts?.axelar_gas_service?.address), { type: 'address' }); @@ -139,7 +143,8 @@ async function uploadWasm(filePath, wallet, chain) { async function upgrade(options, _, chain, contractName) { const { wasmPath, yes } = options; - const contractAddress = chain.contracts[contractName].address; + let contractAddress = chain.contracts[contractName]?.address; + const upgraderAddress = chain.contracts.upgrader?.address; const wallet = await getWallet(chain, options); if (prompt(`Proceed with upgrade on ${chain.name}?`, yes)) { @@ -147,22 +152,49 @@ async function upgrade(options, _, chain, contractName) { } validateParameters({ - isNonEmptyString: { contractAddress }, + isNonEmptyString: { contractAddress, upgraderAddress }, }); + contractAddress = Address.fromString(contractAddress); + const newWasmHash = await uploadWasm(wasmPath, wallet, chain); printInfo('New Wasm hash', serializeValue(newWasmHash)); const operation = Operation.invokeContractFunction({ - contract: contractAddress, + contract: chain.contracts.upgrader.address, function: 'upgrade', - args: [nativeToScVal(newWasmHash)], + args: [contractAddress, options.newVersion, newWasmHash, [options.migrationData]].map(nativeToScVal), + auth: await createUpgradeAuths(contractAddress, newWasmHash, options.migrationData, chain, wallet), }); + await broadcast(operation, wallet, chain, 'Upgraded contract', options); chain.contracts[contractName].wasmHash = serializeValue(newWasmHash); printInfo('Contract upgraded successfully!', contractAddress); } +async function createUpgradeAuths(contractAddress, newWasmHash, migrationData, chain, wallet) { + // 20 seems a reasonable number of ledgers to allow for the upgrade to take effect + const validUntil = await new rpc.Server(chain.rpc).getLatestLedger().then((info) => info.sequence + 20); + + return Promise.all( + [ + createAuthorizedFunc(contractAddress, 'upgrade', [nativeToScVal(newWasmHash)]), + createAuthorizedFunc(contractAddress, 'migrate', [nativeToScVal(migrationData)]), + ].map((auth) => + authorizeInvocation( + wallet, + validUntil, + new xdr.SorobanAuthorizedInvocation({ + function: auth, + subInvocations: [], + }), + wallet.publicKey(), + getNetworkPassphrase(chain.networkType), + ), + ), + ); +} + async function mainProcessor(options, processor, contractName) { const config = loadConfig(options.env); const chain = getChainConfig(config, options.chainName); @@ -200,6 +232,8 @@ function main() { .description('Upgrade a Stellar contract') .argument('', 'contract name to deploy') .addOption(new Option('--wasm-path ', 'path to the WASM file')) + .addOption(new Option('--new-version ', 'new version of the contract')) + .addOption(new Option('--migration-data ', 'migration data').default('()')) .action((contractName, options) => { mainProcessor(options, upgrade, contractName); }); diff --git a/stellar/utils.js b/stellar/utils.js index a748cc0e..97290665 100644 --- a/stellar/utils.js +++ b/stellar/utils.js @@ -9,6 +9,7 @@ const { BASE_FEE, xdr: { DiagnosticEvent, SorobanTransactionData }, Address, + xdr, } = require('@stellar/stellar-sdk'); const { printInfo, sleep, addEnvOption } = require('../common'); const { Option } = require('commander'); @@ -281,6 +282,15 @@ function serializeValue(value) { return value; } +const createAuthorizedFunc = (contractAddress, functionName, args) => + xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn( + new xdr.InvokeContractArgs({ + contractAddress: contractAddress.toScAddress(), + functionName, + args, + }), + ); + module.exports = { stellarCmd, ASSET_TYPE_NATIVE, @@ -295,4 +305,5 @@ module.exports = { getAmplifierVerifiers, serializeValue, getBalances, + createAuthorizedFunc, };