diff --git a/stellar/deploy-contract.js b/stellar/deploy-contract.js index 23b23f59..86d30b83 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 } = 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,7 @@ async function uploadWasm(filePath, wallet, chain) { async function upgrade(options, _, chain, contractName) { const { wasmPath, yes } = options; - const contractAddress = chain.contracts[contractName].address; + const uncheckedContractAddress = chain.contracts[contractName].address; const wallet = await getWallet(chain, options); if (prompt(`Proceed with upgrade on ${chain.name}?`, yes)) { @@ -147,22 +151,63 @@ async function upgrade(options, _, chain, contractName) { } validateParameters({ - isNonEmptyString: { contractAddress }, + isNonEmptyString: { uncheckedContractAddress }, }); + const contractAddress = Address.fromString(uncheckedContractAddress); + 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: [ + nativeToScVal(contractAddress), + nativeToScVal(options.newVersion), + nativeToScVal(newWasmHash), + nativeToScVal([options.migrationData]), + ], + 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), + ), + ), + ); +} + +const createAuthorizedFunc = (contractAddress, functionName, args) => + xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn( + new xdr.InvokeContractArgs({ + contractAddress: contractAddress.toScAddress(), + functionName, + args, + }), + ); + async function mainProcessor(options, processor, contractName) { const config = loadConfig(options.env); const chain = getChainConfig(config, options.chainName); @@ -200,6 +245,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')) .action((contractName, options) => { mainProcessor(options, upgrade, contractName); });