From 8df36a9e65e0bf4b9db04b4049f1f97cf6af973a Mon Sep 17 00:00:00 2001 From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:23:29 +0100 Subject: [PATCH] upgrade program command --- .../networks/.env.local | 11 ++- .../src/commands/abstract/upgrade.ts | 70 +++++++++++++++++++ .../contracts/accessController/index.ts | 2 + .../src/commands/contracts/ocr2/index.ts | 2 + .../src/commands/contracts/store/index.ts | 2 + .../src/lib/constants.ts | 3 + .../src/lib/utils.ts | 27 ++++++- 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 gauntlet/packages/gauntlet-solana-contracts/src/commands/abstract/upgrade.ts diff --git a/gauntlet/packages/gauntlet-serum-multisig/networks/.env.local b/gauntlet/packages/gauntlet-serum-multisig/networks/.env.local index 20974d106..910a0c762 100644 --- a/gauntlet/packages/gauntlet-serum-multisig/networks/.env.local +++ b/gauntlet/packages/gauntlet-serum-multisig/networks/.env.local @@ -1 +1,10 @@ -NODE_URL=http://127.0.0.1:8899 \ No newline at end of file +NODE_URL=http://127.0.0.1:8899 + +PROGRAM_ID_MULTISIG=3X4UvrX9gTxSmNmXww53W5dM5fT1Nby246hKV8Mx9LuD +PROGRAM_ID_OCR2=CF13pnKGJ1WJZeEgVAtFdUi4MMndXm9hneiHs8azUaZt +PROGRAM_ID_ACCESS_CONTROLLER=2F5NEkMnCRkmahEAcQfTQcZv1xtGgrWFfjENtTwHLuKg +PROGRAM_ID_STORE=A7Jh2nb1hZHwqEofm4N8SXbKTj82rx7KUfjParQXUyMQ + + +MULTISIG_ADDRESS= +MULTISIG_SIGNER= \ No newline at end of file diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/abstract/upgrade.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/abstract/upgrade.ts new file mode 100644 index 000000000..f79b375be --- /dev/null +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/abstract/upgrade.ts @@ -0,0 +1,70 @@ +import { Result } from '@chainlink/gauntlet-core' +import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' +import { SolanaCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-solana' +import { AccountMeta, PublicKey, SYSVAR_RENT_PUBKEY, SYSVAR_CLOCK_PUBKEY } from '@solana/web3.js' +import { UPGRADEABLE_BPF_LOADER_PROGRAM_ID } from '../../lib/constants' +import { CONTRACT_LIST, getContract } from '../../lib/contracts' +import { SolanaConstructor } from '../../lib/types' +import { encodeInstruction, makeTx } from '../../lib/utils' + +export const makeUpgradeProgramCommand = (contractId: CONTRACT_LIST): SolanaConstructor => { + return class UpgradeProgram extends SolanaCommand { + static id = `${contractId}:upgrade_program` + static category = contractId + + static examples = [`yarn gauntlet ${contractId}:upgrade_program --network=devnet --buffer=[BUFFER_ACCOUNT]`] + + constructor(flags, args) { + super(flags, args) + + this.require(!!this.flags.buffer, 'Please provide flags with "buffer"') + } + + makeRawTransaction = async (signer: PublicKey) => { + const contract = getContract(contractId, '') + + const programId = new PublicKey(contract.programId) + const [programDataKey, _nonce] = await PublicKey.findProgramAddress( + [programId.toBuffer()], + UPGRADEABLE_BPF_LOADER_PROGRAM_ID, + ) + + const buffer = new PublicKey(this.flags.buffer) + const data = encodeInstruction({ Upgrade: {} }) + + const accounts: AccountMeta[] = [ + { pubkey: programDataKey, isSigner: false, isWritable: true }, + { pubkey: programId, isSigner: false, isWritable: true }, + { pubkey: buffer, isSigner: false, isWritable: true }, + { pubkey: signer, isSigner: false, isWritable: true }, + { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: signer, isSigner: true, isWritable: false }, + ] + + const rawTx: RawTransaction = { + data, + accounts, + programId: UPGRADEABLE_BPF_LOADER_PROGRAM_ID, + } + + return [rawTx] + } + + execute = async () => { + const rawTx = await this.makeRawTransaction(this.wallet.payer.publicKey) + await prompt(`Continue upgrading the ${contractId} program?`) + logger.loading('Upgrading program...') + const txhash = await this.provider.send(makeTx(rawTx), [this.wallet.payer]) + logger.success(`Program upgraded on tx ${txhash}`) + return { + responses: [ + { + tx: this.wrapResponse(txhash, this.flags.state), + contract: this.flags.state, + }, + ], + } as Result + } + } +} diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/accessController/index.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/accessController/index.ts index 22d90ae5b..3ed0b5513 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/accessController/index.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/accessController/index.ts @@ -4,6 +4,7 @@ import ReadState from './read' import { makeAcceptOwnershipCommand } from '../ownership/acceptOwnership' import { makeTransferOwnershipCommand } from '../ownership/transferOwnership' import { CONTRACT_LIST } from '../../../lib/contracts' +import { makeUpgradeProgramCommand } from '../../abstract/upgrade' export default [ Initialize, @@ -11,4 +12,5 @@ export default [ ReadState, makeAcceptOwnershipCommand(CONTRACT_LIST.ACCESS_CONTROLLER), makeTransferOwnershipCommand(CONTRACT_LIST.ACCESS_CONTROLLER), + makeUpgradeProgramCommand(CONTRACT_LIST.ACCESS_CONTROLLER), ] diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts index 932d189c6..9b81ac3e0 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts @@ -19,6 +19,7 @@ import Inspection from './inspection' import { makeAcceptOwnershipCommand } from '../ownership/acceptOwnership' import { CONTRACT_LIST } from '../../../lib/contracts' import { makeTransferOwnershipCommand } from '../ownership/transferOwnership' +import { makeUpgradeProgramCommand } from '../../abstract/upgrade' export default [ Initialize, @@ -43,4 +44,5 @@ export default [ SetupRDDFlow, makeAcceptOwnershipCommand(CONTRACT_LIST.OCR_2), makeTransferOwnershipCommand(CONTRACT_LIST.OCR_2), + makeUpgradeProgramCommand(CONTRACT_LIST.OCR_2), ] diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/store/index.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/store/index.ts index ab725763d..0ed092f45 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/store/index.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/store/index.ts @@ -6,6 +6,7 @@ import SetLoweringAccessController from './setLoweringAccessController' import { makeAcceptOwnershipCommand } from '../ownership/acceptOwnership' import { makeTransferOwnershipCommand } from '../ownership/transferOwnership' import { CONTRACT_LIST } from '../../../lib/contracts' +import { makeUpgradeProgramCommand } from '../../abstract/upgrade' export default [ Initialize, @@ -15,4 +16,5 @@ export default [ SetLoweringAccessController, makeAcceptOwnershipCommand(CONTRACT_LIST.STORE), makeTransferOwnershipCommand(CONTRACT_LIST.STORE), + makeUpgradeProgramCommand(CONTRACT_LIST.STORE), ] diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/lib/constants.ts b/gauntlet/packages/gauntlet-solana-contracts/src/lib/constants.ts index cc5ae7e60..bb1eb7379 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/lib/constants.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/lib/constants.ts @@ -1,2 +1,5 @@ +import { PublicKey } from '@solana/web3.js' + export const MAX_TRANSACTION_BYTES = 996 export const ORACLES_MAX_LENGTH = 19 +export const UPGRADEABLE_BPF_LOADER_PROGRAM_ID = new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111') diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/lib/utils.ts b/gauntlet/packages/gauntlet-solana-contracts/src/lib/utils.ts index a4c14dc14..b54ad2133 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/lib/utils.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/lib/utils.ts @@ -1,5 +1,6 @@ -import { Transaction, TransactionInstruction, Keypair } from '@solana/web3.js' +import { Transaction, TransactionInstruction } from '@solana/web3.js' import { RawTransaction } from '@chainlink/gauntlet-solana' +import * as BufferLayout from '@solana/buffer-layout' export const divideIntoChunks = (arr: Array | Buffer, chunkSize: number): any[][] => { const chunks: any[] = [] @@ -24,3 +25,27 @@ export const makeTx = (rawTx: RawTransaction[]): Transaction => { new Transaction(), ) } + +// Source: https://github.com/neonlabsorg/multisig/blob/8f1938c82c8db1251fad48a403487af18ecf5eb0/client/loader.ts#L25 +export const encodeInstruction = (data: any): Buffer => { + const CHUNK_SIZE = 900 + + const dataLayout = BufferLayout.union(BufferLayout.u32('tag'), null, 'tag') + dataLayout.addVariant(0, BufferLayout.struct([]), 'InitializeBuffer') + const write = BufferLayout.struct([ + BufferLayout.u32('offset'), + BufferLayout.nu64('length'), + BufferLayout.seq(BufferLayout.u8('byte'), BufferLayout.offset(BufferLayout.u32(), -8), 'bytes'), + ]) + dataLayout.addVariant(1, write, 'Write') + const deployWithMaxLen = BufferLayout.struct([BufferLayout.nu64('max_data_len')]) + dataLayout.addVariant(2, deployWithMaxLen, 'DeployWithMaxDataLen') + dataLayout.addVariant(3, BufferLayout.struct([]), 'Upgrade') + dataLayout.addVariant(4, BufferLayout.struct([]), 'SetAuthority') + dataLayout.addVariant(5, BufferLayout.struct([]), 'Close') + + // UpgradeableLoaderInstruction tag + offset + chunk length + chunk data + const instructionBuffer = Buffer.alloc(4 + 4 + 8 + CHUNK_SIZE) + const encodedSize = dataLayout.encode(data, instructionBuffer) + return instructionBuffer.slice(0, encodedSize) +}