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

Local and Ledger wallet support #167

Merged
merged 6 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 34 additions & 22 deletions gauntlet/packages/gauntlet-serum-multisig/src/commands/multisig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SolanaCommand, RawTransaction } from '@chainlink/gauntlet-solana'
import { logger, BN, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { PublicKey, SYSVAR_RENT_PUBKEY, Keypair, AccountMeta, Transaction } from '@solana/web3.js'
import { PublicKey, SYSVAR_RENT_PUBKEY, Keypair, AccountMeta, SystemProgram } from '@solana/web3.js'
import { CONTRACT_LIST, getContract, makeTx } from '@chainlink/gauntlet-solana-contracts'
import { Idl, Program } from '@project-serum/anchor'
import { MAX_BUFFER_SIZE } from '../lib/constants'
Expand All @@ -26,32 +26,27 @@ export const wrapCommand = (command) => {
logger.info(`Running ${command.id} command using Serum Multisig`)

this.command = new command({ ...flags, bufferSize: MAX_BUFFER_SIZE }, args)
this.command.invokeMiddlewares(this.command, this.command.middlewares)
this.require(!!process.env.MULTISIG_ADDRESS, 'Please set MULTISIG_ADDRESS env var')
this.multisigAddress = new PublicKey(process.env.MULTISIG_ADDRESS)
}

execute = async () => {
// TODO: Command underneath will try to load its own provider and wallet if invoke middlewares, but we should be able to specify which ones to use, in an obvious better way
this.command.provider = this.provider
this.command.wallet = this.wallet

const multisig = getContract(CONTRACT_LIST.MULTISIG, '')
this.program = this.loadProgram(multisig.idl, multisig.programId.toString())

// Falling back on default wallet if signer is not provided or execute flag is provided
const signer = this.flags.execute
? this.wallet.payer.publicKey
: new PublicKey(this.flags.signer || this.wallet.payer.publicKey)
const signer = this.wallet.publicKey
const rawTxs = await this.makeRawTransaction(signer)
// If proposal is not provided, we are at creation time, and a new proposal acc should have been created
const proposal = new PublicKey(this.flags.proposal || rawTxs[0].accounts[1].pubkey)
const latestSlot = await this.provider.connection.getSlot()
const recentBlock = await this.provider.connection.getBlock(latestSlot)
const tx = makeTx(rawTxs, {
recentBlockhash: recentBlock.blockhash,
feePayer: signer,
})

if (this.flags.execute) {
await prompt('CREATION,APPROVAL or EXECUTION TX will be executed. Continue?')
logger.loading(`Executing action...`)
const txhash = await this.provider.send(tx, [this.wallet.payer])
const txhash = await this.sendTxWithIDL(this.signAndSendRawTx, this.program.idl)(rawTxs)
await this.inspectProposalState(proposal)
return {
responses: [
Expand All @@ -63,15 +58,22 @@ export const wrapCommand = (command) => {
}
}

const txData = tx.compileMessage().serialize().toString('base64')
const latestSlot = await this.provider.connection.getSlot()
const recentBlock = await this.provider.connection.getBlock(latestSlot)
const tx = makeTx(rawTxs, {
recentBlockhash: recentBlock.blockhash,
feePayer: signer,
})

const msgData = tx.serializeMessage().toString('base64')
logger.line()
logger.success(
`Transaction generated with blockhash ID: ${recentBlock.blockhash.toString()} (${new Date(
`Message generated with blockhash ID: ${recentBlock.blockhash.toString()} (${new Date(
recentBlock.blockTime * 1000,
).toLocaleString()}). TX DATA:`,
).toLocaleString()}). MESSAGE DATA:`,
)
logger.log()
logger.log(txData)
logger.log(msgData)
logger.log()
logger.line()

Expand All @@ -81,7 +83,7 @@ export const wrapCommand = (command) => {
tx: this.wrapResponse('', this.multisigAddress.toString()),
contract: this.multisigAddress.toString(),
data: {
transactionData: txData,
transactionData: msgData,
},
},
],
Expand Down Expand Up @@ -153,7 +155,7 @@ export const wrapCommand = (command) => {
try {
return await this.program.account.transaction.fetch(proposal)
} catch (e) {
logger.info('Proposal state not found')
logger.info('Proposal state not found. Should be empty at CREATION time')
return
}
}
Expand All @@ -175,9 +177,19 @@ export const wrapCommand = (command) => {
logger.log('Creating proposal account...')
const proposal = Keypair.generate()
const txSize = 1300 // Space enough
const proposalAccount = await this.program.account.transaction.createInstruction(proposal, txSize)
const accountTx = new Transaction().add(proposalAccount)
await this.provider.send(accountTx, [proposal, this.wallet.payer])
const proposalInstruction = await SystemProgram.createAccount({
fromPubkey: this.wallet.publicKey,
newAccountPubkey: proposal.publicKey,
space: txSize,
lamports: await this.provider.connection.getMinimumBalanceForRentExemption(txSize),
programId: this.program.programId,
})
const rawTx: RawTransaction = {
data: proposalInstruction.data,
accounts: proposalInstruction.keys,
programId: proposalInstruction.programId,
}
await this.signAndSendRawTx([rawTx], [proposal])
logger.success(`Proposal account created at: ${proposal.publicKey.toString()}`)
return proposal.publicKey
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { SolanaCommand, RawTransaction, TransactionResponse } from '@chainlink/g
import { PublicKey } from '@solana/web3.js'
import { CONTRACT_LIST, getContract } from '@chainlink/gauntlet-solana-contracts'
import { Result } from '@chainlink/gauntlet-core'
import { logger } from '@chainlink/gauntlet-core/dist/utils'

export default class SetOwners extends SolanaCommand {
static id = 'set:owners'
static id = 'set_owners'
static category = CONTRACT_LIST.MULTISIG

static examples = [
'yarn gauntlet-serum-multisig set:owners --network=local --approve --tx=9Vck9Gdk8o9WhxT8bgNcfJ5gbvFBN1zPuXpf8yu8o2aq --execute AGnZeMWkdyXBiLDG2DnwuyGSviAbCGJXyk4VhvP9Y51M QMaHW2Fpyet4ZVf7jgrGB6iirZLjwZUjN9vPKcpQrHs',
]
static examples = ['yarn gauntlet-serum-multisig set_owners --network=local']

constructor(flags, args) {
super(flags, args)
Expand All @@ -19,8 +18,13 @@ export default class SetOwners extends SolanaCommand {
const multisig = getContract(CONTRACT_LIST.MULTISIG, '')
const address = multisig.programId.toString()
const program = this.loadProgram(multisig.idl, address)

const owners = this.args.map((a) => new PublicKey(a))

logger.info(`Generating data for new owners: ${owners.map((o) => o.toString())}`)

const data = program.coder.instruction.encode('set_owners', {
owners: this.args.map((a) => new PublicKey(a)),
owners,
})

const accounts = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import { SolanaCommand, RawTransaction, TransactionResponse } from '@chainlink/g
import { PublicKey } from '@solana/web3.js'
import { CONTRACT_LIST, getContract } from '@chainlink/gauntlet-solana-contracts'
import { Result } from '@chainlink/gauntlet-core'
import { BN } from '@chainlink/gauntlet-core/dist/utils'
import { BN, logger } from '@chainlink/gauntlet-core/dist/utils'

export default class SetThreshold extends SolanaCommand {
static id = 'set:threshold'
static id = 'set_threshold'
static category = CONTRACT_LIST.MULTISIG

static examples = [
'yarn gauntlet-serum-multisig set:threshold --network=local --threshold=2 --approve --tx=9Vck9Gdk8o9WhxT8bgNcfJ5gbvFBN1zPuXpf8yu8o2aq --execute',
]
static examples = ['yarn gauntlet-serum-multisig set_threshold --network=local --threshold=2']

constructor(flags, args) {
super(flags, args)
Expand All @@ -22,8 +20,12 @@ export default class SetThreshold extends SolanaCommand {
const multisig = getContract(CONTRACT_LIST.MULTISIG, '')
const address = multisig.programId.toString()
const program = this.loadProgram(multisig.idl, address)

const threshold = new BN(this.flags.threshold)
logger.info(`Generating data for new threshold: ${threshold.toNumber()}`)

const data = program.coder.instruction.encode('change_threshold', {
threshold: new BN(this.flags.threshold),
threshold,
})

const accounts = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export default class ReadState extends SolanaCommand {
// read could be abstract. account.accessController is just the name of the account that can be got form the camelcase(schema.accounts[x].name)
const data = await program.account.accessController.fetch(state)

console.log(data)
console.log(`
- Owner: ${new PublicKey(data.owner).toString()}
- Proposed Owner: ${new PublicKey(data.proposedOwner).toString()}
`)
return {} as Result<TransactionResponse>
}
}
Loading