Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multisig support for more commands #115

Merged
merged 25 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0bcd808
Draft multisig support for setConfig, setPayees, setWriter
ebarakos Jan 18, 2022
eb41ca9
Camel case fix + begin_offchain_config command
ebarakos Jan 18, 2022
3e02d64
write offchain config
ebarakos Jan 18, 2022
c4331af
Offchain config commands supported(need to add multiple txs on write)
ebarakos Jan 18, 2022
42d85a6
SetValidatorConfig supported
ebarakos Jan 18, 2022
30f92cd
SetConfig should work
ebarakos Jan 18, 2022
27119e4
SetPayees should work
ebarakos Jan 18, 2022
f31f25e
Prettier
ebarakos Jan 18, 2022
2fbe1a6
Revert chunks on write_offchain_config
ebarakos Jan 18, 2022
7fa1a24
Remove commented code
ebarakos Jan 18, 2022
e8dc822
SetWriter fixed
ebarakos Jan 18, 2022
1588871
Error translation wrapping, removing tx creation repetion, address co…
ebarakos Jan 19, 2022
25d69bd
Merge remote-tracking branch 'origin/develop' into multisig-ocr-commands
ebarakos Jan 19, 2022
645874a
Prettier
ebarakos Jan 19, 2022
fb6ca15
Support transfer/accept ownership commands
ebarakos Jan 19, 2022
adbbdeb
Prettier
ebarakos Jan 19, 2022
af6655e
Misc
ebarakos Jan 20, 2022
2a958ab
Merge remote-tracking branch 'origin/develop' into multisig-ocr-commands
ebarakos Jan 20, 2022
f379514
Prettier
ebarakos Jan 20, 2022
9ddd426
Addressing comments, README instructions, moved errorwrapping to Sola…
ebarakos Jan 20, 2022
7a20660
Error translation for multisig
ebarakos Jan 20, 2022
211e687
Review comments
ebarakos Jan 20, 2022
edc11eb
Merge remote-tracking branch 'origin/develop' into multisig-tx-simula…
ebarakos Jan 21, 2022
70573b3
Review comments
ebarakos Jan 21, 2022
e61ee2b
Review comments
ebarakos Jan 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ bin
# Gauntlet artifacts
gauntlet/packages/gauntlet-solana-contracts/artifacts/programId
gauntlet/packages/gauntlet-solana-contracts/artifacts/bin
# Multisig local env
gauntlet/packages/gauntlet-serum-multisig/networks/.env.local
6 changes: 6 additions & 0 deletions gauntlet/packages/gauntlet-serum-multisig/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Gauntlet Serum Multisig

Current version 0.7.0

Artifacts: https://github.com/project-serum/multisig/releases/tag/v0.7.0

## Creating a Multisig

Example with 3 owners and threshold=2

`yarn gauntlet-serum-multisig create --network=local 3W37Aopzbtzczi8XWdkFTvBeSyYgXLuUkaodkq59xBCT ETqajtkz4xcsB397qTBPetprR8jMC3JszkjJJp3cjWJS QMaHW2Fpyet4ZVf7jgrGB6iirZLjwZUjN9vPKcpQrHs --threshold=2`

You will get 2 addresses, Multisig address and Multisig Signer. Please keep them both as they will be needed. Signer will be used when granting access/ownership to multisig.

## Actions

Rest of the commands will adhere to the following flow:
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export default class MultisigCreate extends SolanaCommand {
static id = 'create'
static category = CONTRACT_LIST.MULTISIG

static examples = ['yarn gauntlet-serum-multisig create --network=local']
static examples = [
'yarn gauntlet-serum-multisig create --network=local 3W37Aopzbtzczi8XWdkFTvBeSyYgXLuUkaodkq59xBCT ETqajtkz4xcsB397qTBPetprR8jMC3JszkjJJp3cjWJS QMaHW2Fpyet4ZVf7jgrGB6iirZLjwZUjN9vPKcpQrHs --threshold=2',
]

constructor(flags, args) {
super(flags, args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TransactionResponse, SolanaCommand, RawTransaction } from '@chainlink/g
import { logger, BN } from '@chainlink/gauntlet-core/dist/utils'
import { PublicKey, SYSVAR_RENT_PUBKEY, Keypair } from '@solana/web3.js'
import { CONTRACT_LIST, getContract } from '@chainlink/gauntlet-solana-contracts'
import { Idl, Program } from '@project-serum/anchor'
import { ProgramError, parseIdlErrors, Idl, Program } from '@project-serum/anchor'

type ProposalContext = {
rawTx: RawTransaction
Expand Down Expand Up @@ -45,6 +45,7 @@ export const wrapCommand = (command) => {
[this.multisigAddress.toBuffer()],
this.program.programId,
)

const multisigState = await this.program.account.multisig.fetch(process.env.MULTISIG_ADDRESS)
const threshold = multisigState.threshold
const owners = multisigState.owners
Expand Down Expand Up @@ -90,9 +91,7 @@ export const wrapCommand = (command) => {
this.inspectProposalState(proposal, threshold, owners)
return result
}

const result = await this.wrapAction(this.executeProposal)(proposal, proposalContext)
this.inspectProposalState(proposal, threshold, owners)
return result
}

Expand All @@ -114,7 +113,7 @@ export const wrapCommand = (command) => {
} else {
logger.error(e)
}
return {} as Result<TransactionResponse>
throw e
}
}

Expand Down Expand Up @@ -153,23 +152,34 @@ export const wrapCommand = (command) => {

executeProposal: ProposalAction = async (proposal: PublicKey, context): Promise<string> => {
logger.loading(`Executing proposal`)

const tx = await this.program.rpc.executeTransaction({
accounts: {
multisig: this.multisigAddress,
multisigSigner: context.multisigSigner,
transaction: proposal,
},
remainingAccounts: context.proposalState.accounts
.map((t) => (t.pubkey.equals(context.multisigSigner) ? { ...t, isSigner: false } : t))
.concat({
pubkey: context.proposalState.programId,
isWritable: false,
isSigner: false,
}),
})
logger.info(`Execution TX hash: ${tx.toString()}`)
return tx
// get the command's starting word to map it to the respective IDL(ocr2, store etc)
const { idl } = getContract(command.id.split(':')[0], '')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not good. This could be addressed when we change this.
For the moment, leave it as it was

For the moment let's load the contract and program here too. Open an issue please to circle back here. You can use program.idl then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue here if I remove this, is that errors will not be translated properly for multisig commands, leading to confusion for users.

Maybe open an issue for this as well? Tackling it along with the other by keeping some program/idl state on SolanaCommand could be possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, open an issue please. I'd say that the same issue can solve both problems

try {
const tx = await this.program.rpc.executeTransaction({
accounts: {
multisig: this.multisigAddress,
multisigSigner: context.multisigSigner,
transaction: proposal,
},
remainingAccounts: context.proposalState.accounts
.map((t) => (t.pubkey.equals(context.multisigSigner) ? { ...t, isSigner: false } : t))
.concat({
pubkey: context.proposalState.programId,
isWritable: false,
isSigner: false,
}),
})
logger.info(`Execution TX hash: ${tx.toString()}`)
return tx
} catch (err) {
// Translate IDL error
const idlErrors = parseIdlErrors(idl)
let translatedErr = ProgramError.parse(err, idlErrors)
if (translatedErr === null) {
throw err
}
throw translatedErr
}
}

inspectProposalState = async (proposal, threshold, owners) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { PublicKey } from '@solana/web3.js'
import { SolanaCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-solana'
import { AccountMeta, PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
import { CONTRACT_LIST, getContract } from '../../../../lib/contracts'
import { makeTx } from '../../../../lib/utils'

export default class BeginOffchainConfig extends SolanaCommand {
static id = 'ocr2:begin_offchain_config'
Expand All @@ -19,31 +20,56 @@ export default class BeginOffchainConfig extends SolanaCommand {
this.require(!!this.flags.state, 'Please provide flags with "state"')
}

execute = async () => {
makeRawTransaction = async (signer: PublicKey) => {
const ocr2 = getContract(CONTRACT_LIST.OCR_2, '')
const address = ocr2.programId.toString()
const program = this.loadProgram(ocr2.idl, address)

const state = new PublicKey(this.flags.state)
const owner = this.wallet.payer
const version = new BN(2)

await prompt(`Begin setting Offchain config version ${version.toString()}?`)
const data = program.coder.instruction.encode('begin_offchain_config', {
offchainConfigVersion: version,
})

const tx = await program.rpc.beginOffchainConfig(version, {
accounts: {
state: state,
authority: owner.publicKey,
const accounts: AccountMeta[] = [
{
pubkey: state,
isSigner: false,
isWritable: true,
},
})
{
pubkey: signer,
isSigner: true,
isWritable: false,
},
]

const rawTx: RawTransaction = {
data,
accounts,
programId: ocr2.programId,
}

logger.success(`Begin set offchain config on tx ${tx}`)
return [rawTx]
}

execute = async () => {
const contract = getContract(CONTRACT_LIST.OCR_2, '')
const rawTx = await this.makeRawTransaction(this.wallet.payer.publicKey)
const tx = makeTx(rawTx)
logger.debug(tx)
const version = new BN(2)
await prompt(`Begin setting Offchain config version ${version.toString()}?`)
logger.loading('Sending tx...')
const txhash = await this.sendTx(tx, [this.wallet.payer], contract.idl)
logger.success(`Begin set offchain config on tx hash: ${txhash}`)

return {
responses: [
{
tx: this.wrapResponse(tx, state.toString(), { state: state.toString() }),
contract: state.toString(),
tx: this.wrapResponse(txhash, this.flags.state),
contract: this.flags.state,
},
],
} as Result<TransactionResponse>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { PublicKey } from '@solana/web3.js'
import { SolanaCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-solana'
import { AccountMeta, PublicKey } from '@solana/web3.js'
import { CONTRACT_LIST, getContract } from '../../../../lib/contracts'
import { makeTx } from '../../../../lib/utils'

export default class CommitOffchainConfig extends SolanaCommand {
static id = 'ocr2:commit_offchain_config'
Expand All @@ -18,30 +19,53 @@ export default class CommitOffchainConfig extends SolanaCommand {
this.require(!!this.flags.state, 'Please provide flags with "state"')
}

execute = async () => {
makeRawTransaction = async (signer: PublicKey) => {
const ocr2 = getContract(CONTRACT_LIST.OCR_2, '')
const address = ocr2.programId.toString()
const program = this.loadProgram(ocr2.idl, address)

const state = new PublicKey(this.flags.state)
const owner = this.wallet.payer

await prompt(`Commit Offchain config?`)
const data = program.coder.instruction.encode('commit_offchain_config', {})

const tx = await program.rpc.commitOffchainConfig({
accounts: {
state: state,
authority: owner.publicKey,
const accounts: AccountMeta[] = [
{
pubkey: state,
isSigner: false,
isWritable: true,
},
})
{
pubkey: signer,
isSigner: true,
isWritable: false,
},
]

const rawTx: RawTransaction = {
data,
accounts,
programId: ocr2.programId,
}

return [rawTx]
}

execute = async () => {
const contract = getContract(CONTRACT_LIST.OCR_2, '')
const rawTx = await this.makeRawTransaction(this.wallet.payer.publicKey)
const tx = makeTx(rawTx)
logger.debug(tx)
await prompt(`Commit Offchain config?`)
logger.loading('Sending tx...')
const txhash = await this.sendTx(tx, [this.wallet.payer], contract.idl)
logger.success(`Committing offchain config on tx ${tx}`)

return {
responses: [
{
tx: this.wrapResponse(tx, state.toString(), { state: state.toString() }),
contract: state.toString(),
tx: this.wrapResponse(txhash, this.flags.state),
contract: this.flags.state,
},
],
} as Result<TransactionResponse>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Result } from '@chainlink/gauntlet-core'
import { SolanaCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-solana'
import { AccountMeta, PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js'
import { AccountMeta, PublicKey } from '@solana/web3.js'
import { logger, BN, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { CONTRACT_LIST, getContract } from '../../../lib/contracts'
import { getRDD } from '../../../lib/rdd'
import { makeTx } from '../../../lib/utils'

type Input = {
observationPaymentGjuels: number | string
Expand Down Expand Up @@ -47,7 +48,6 @@ export default class SetBilling extends SolanaCommand {
const billingAC = new PublicKey(info.config.billingAccessController)
logger.loading('Generating billing tx information...')
logger.log('Billing information:', input)
await prompt('Continue setting billing?')
const data = program.coder.instruction.encode('set_billing', {
observationPaymentGjuels: new BN(input.observationPaymentGjuels),
transmissionPaymentGjuels: new BN(input.transmissionPaymentGjuels),
Expand Down Expand Up @@ -81,21 +81,13 @@ export default class SetBilling extends SolanaCommand {
}

execute = async () => {
const contract = getContract(CONTRACT_LIST.OCR_2, '')
const rawTx = await this.makeRawTransaction(this.wallet.payer.publicKey)
const tx = rawTx.reduce(
(tx, meta) =>
tx.add(
new TransactionInstruction({
programId: meta.programId,
keys: meta.accounts,
data: meta.data,
}),
),
new Transaction(),
)

const tx = makeTx(rawTx)
logger.debug(tx)
await prompt('Continue setting billing?')
logger.loading('Sending tx...')
const txhash = await this.provider.send(tx, [this.wallet.payer])
const txhash = await this.sendTx(tx, [this.wallet.payer], contract.idl)
logger.success(`Billing set on tx hash: ${txhash}`)

return {
Expand Down
Loading