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 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import SetBillingAccessController from './setBillingAccessController'
import SetRequesterAccessController from './setRequesterAccessController'
import SetBilling from './setBilling'
import SetConfig from './setConfig'
import SetConfigMultisig from './setConfigMultisig'
import SetPayees from './setPayees'
import SetPayeesMultisig from './setPayeesMultisig'
import SetupFlow from './setup.dev.flow'
import SetupRDDFlow from './setup.dev.rdd.flow'
import Transmit from './transmit.dev'
Expand All @@ -30,6 +32,8 @@ export default [
CommitOffchainConfig,
SetBillingAccessController,
SetRequesterAccessController,
SetConfigMultisig,
SetPayeesMultisig,
// Inspection
...Inspection,
// ONLY DEV
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ export default class SetBilling extends SolanaCommand {
),
new Transaction(),
)

logger.loading('Sending tx...')
logger.debug(tx)
const txhash = await this.provider.send(tx, [this.wallet.payer])
logger.success(`Billing set on tx hash: ${txhash}`)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, BN, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-solana'
import { AccountMeta, PublicKey, TransactionInstruction, Transaction } from '@solana/web3.js'
import { ORACLES_MAX_LENGTH } from '../../../lib/constants'
import { CONTRACT_LIST, getContract } from '../../../lib/contracts'
import { getRDD } from '../../../lib/rdd'

type Input = {
oracles: {
signer: string
transmitter: string
}[]
f: number | string
}
export default class SetConfigNew extends SolanaCommand {
static id = 'multisig:ocr2:set_config'
static category = CONTRACT_LIST.OCR_2

static examples = [
'yarn gauntlet ocr2:set_config --network=devnet --state=EPRYwrb1Dwi8VT5SutS4vYNdF8HqvE7QwvqeCCwHdVLC',
]

makeInput = (userInput): Input => {
if (userInput) return userInput as Input
const rdd = getRDD(this.flags.rdd)
const aggregator = rdd.contracts[this.flags.state]
const aggregatorOperators: any[] = aggregator.oracles.map((o) => rdd.operators[o.operator])
const oracles = aggregatorOperators.map((operator) => ({
// Same here
transmitter: operator.ocrNodeAddress[0],
signer: operator.ocr2OnchainPublicKey[0].replace('ocr2on_solana_', ''),
}))
const f = aggregator.config.f
return {
oracles,
f,
}
}

constructor(flags, args) {
super(flags, args)

this.require(!!this.flags.state, 'Please provide flags with "state"')
}

makeRawTransaction = async (owner: 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 input = this.makeInput(this.flags.input)

const oracles = input.oracles.map(({ signer, transmitter }) => ({
signer: Buffer.from(signer, 'hex'),
transmitter: new PublicKey(transmitter),
}))
const f = new BN(input.f)

const minOracleLength = f.mul(new BN(3)).toNumber()
this.require(oracles.length > minOracleLength, `Number of oracles should be higher than ${minOracleLength}`)
this.require(
oracles.length <= ORACLES_MAX_LENGTH,
`Oracles max length is ${ORACLES_MAX_LENGTH}, currently ${oracles.length}`,
)

logger.log('Config information:', input)
await prompt(`Continue setting config on ${state.toString()}?`)
console.log(oracles)
const data = program.coder.instruction.encode('set_config', { new_oracles: oracles, f })
ebarakos marked this conversation as resolved.
Show resolved Hide resolved

const accounts: AccountMeta[] = [
{
pubkey: state,
isSigner: false,
isWritable: true,
},
{
pubkey: owner,
isSigner: false,
ebarakos marked this conversation as resolved.
Show resolved Hide resolved
isWritable: false,
},
]

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

return [rawTx]
}

execute = async () => {
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 txhash = await this.provider.send(tx, [this.wallet.payer])
logger.success(`Config set on tx ${tx}`)

return {
responses: [
{
tx: this.wrapResponse(txhash, this.flags.state),
contract: this.flags.state,
},
],
} as Result<TransactionResponse>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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, Transaction, TransactionInstruction } from '@solana/web3.js'
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { CONTRACT_LIST, getContract } from '../../../lib/contracts'
import { getRDD } from '../../../lib/rdd'

type Input = {
operators: {
transmitter: string
payee: string
}[]
// Allows to set payees that do not have a token generated address
allowFundRecipient?: boolean
}

export default class SetPayeesMultisig extends SolanaCommand {
static id = 'multisig:ocr2:set_payees'
static category = CONTRACT_LIST.OCR_2

static examples = [
'yarn gauntlet ocr2:set_payees --network=local --state=EPRYwrb1Dwi8VT5SutS4vYNdF8HqvE7QwvqeCCwHdVLC',
]

makeInput = (userInput: any): Input => {
if (userInput) return userInput as Input
const rdd = getRDD(this.flags.rdd)
const aggregator = rdd.contracts[this.flags.state]
const aggregatorOperators: string[] = aggregator.oracles.map((o) => o.operator)
const operators = aggregatorOperators.map((operator) => ({
transmitter: rdd.operators[operator].ocrNodeAddress[0],
payee: rdd.operators[operator].adminAddress,
}))
return {
operators,
allowFundRecipient: false,
}
}

constructor(flags, args) {
super(flags, args)

this.requireFlag('state', 'Provide a valid state address')
this.requireFlag('link', 'Provide a valid link address')
}

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

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

const info = await program.account.state.fetch(state)
const token = new Token(
this.provider.connection,
new PublicKey(this.flags.link),
TOKEN_PROGRAM_ID,
this.wallet.payer,
)

this.flags.TESTING_ONLY_IGNORE_PAYEE_VALIDATION &&
logger.warn('TESTING_ONLY_IGNORE_PAYEE_VALIDATION flag is enabled')

if (!this.flags.TESTING_ONLY_IGNORE_PAYEE_VALIDATION) {
const areValidPayees = (
await Promise.all(
input.operators.map(async ({ payee }) => {
try {
const info = await token.getAccountInfo(new PublicKey(payee))
return !!info.address
} catch (e) {
logger.error(`Payee with address ${payee} does not have a valid Token recipient address`)
return false
}
}),
)
).every((isValid) => isValid)

this.require(
areValidPayees || !!input.allowFundRecipient,
'Every payee needs to have a valid token recipient address',
)
}
const payeeByTransmitter = input.operators.reduce(
(agg, operator) => ({
...agg,
[new PublicKey(operator.transmitter).toString()]: new PublicKey(operator.payee),
}),
{},
)

// Set the payees in the same order the oracles are saved in the contract. The length of the payees need to be same as the oracles saved
const payees = info.oracles.xs
.slice(0, info.oracles.len)
.map(({ transmitter }) => payeeByTransmitter[new PublicKey(transmitter).toString()])

logger.log('Payees information:', input)
await prompt('Continue setting payees?')
const data = program.coder.instruction.encode('set_payees', {
payees
})

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 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(),
)

logger.loading('Sending tx...')
const txhash = await this.provider.send(tx, [this.wallet.payer])
logger.success(`Payees set on tx hash: ${txhash}`)

return {
responses: [
{
tx: this.wrapResponse(txhash, this.flags.state),
contract: this.flags.state,
},
],
} as Result<TransactionResponse>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Initialize from './initialize'
import CreateFeed from './createFeed'
import SetValidatorConfig from './setValidatorConfig'
import SetWriter from './setWriter'
import SetWriterMultisig from './setWriterMultisig'
import TransferOwnership from './transferOwnership'
import AcceptOwnership from './acceptOwnership'
import SetLoweringAccessController from './setLoweringAccessController'
Expand All @@ -11,6 +12,7 @@ export default [
CreateFeed,
SetValidatorConfig,
SetWriter,
SetWriterMultisig,
TransferOwnership,
AcceptOwnership,
SetLoweringAccessController,
Expand Down
Loading