Skip to content

Commit

Permalink
multisig command
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoAD committed Feb 16, 2022
1 parent 592e0ef commit 1b0aa71
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AbstractCommand, { makeAbstractCommand } from '.'
import { Result } from '@chainlink/gauntlet-core'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
import { MsgExecuteContract } from '@terra-money/terra.js'
import { AccAddress, MsgExecuteContract } from '@terra-money/terra.js'

export interface AbstractInstruction<Input, ContractInput> {
instruction: {
Expand Down Expand Up @@ -38,9 +38,9 @@ export const instructionToCommand = (instruction: AbstractInstruction<any, any>)
return abstractCommand
}

makeRawTransaction = async (): Promise<MsgExecuteContract> => {
makeRawTransaction = async (signer: AccAddress): Promise<MsgExecuteContract> => {
const command = await this.buildCommand()
return command.makeRawTransaction()
return command.makeRawTransaction(signer)
}

execute = async (): Promise<Result<TransactionResponse>> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Result } from '@chainlink/gauntlet-core'
import { MsgExecuteContract } from '@terra-money/terra.js'
import { AccAddress, MsgExecuteContract } from '@terra-money/terra.js'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { TransactionResponse, TerraCommand } from '@chainlink/gauntlet-terra'
import { Contract, CONTRACT_LIST, getContract, TerraABI, TERRA_OPERATIONS } from '../../lib/contracts'
Expand Down Expand Up @@ -126,9 +126,9 @@ export default class AbstractCommand extends TerraCommand {
this.contracts = [this.opts.contract.id]
}

makeRawTransaction = async (): Promise<MsgExecuteContract> => {
makeRawTransaction = async (signer: AccAddress): Promise<MsgExecuteContract> => {
const address = this.args[0]
return new MsgExecuteContract(this.wallet.key.accAddress, address, this.params)
return new MsgExecuteContract(signer, address, this.params)
}

abstractDeploy: AbstractExecute = async (params: any) => {
Expand Down
152 changes: 145 additions & 7 deletions packages-ts/gauntlet-terra-cw20-multisig/src/commands/multisig.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,168 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
import { MsgExecuteContract } from '@terra-money/terra.js'
import { AccAddress, MsgExecuteContract } from '@terra-money/terra.js'

type ProposalAction = (
signer: AccAddress,
proposalId: number,
message: MsgExecuteContract,
) => Promise<MsgExecuteContract>

enum Vote {
YES = 'yes',
NO = 'no',
ABS = 'abstain',
VETO = 'veto',
}

type WasmMsg = {
execute: {
contract_addr: string
funds: {
denom: string
amount: string
}[]
msg: string
}
}

enum Action {
CREATE = 'create',
APPROVE = 'approve',
EXECUTE = 'execute',
}

type State = {
threshold: number
proposalStatus: Action
}

export const wrapCommand = (command) => {
return class Multisig extends TerraCommand {
command: TerraCommand
multisig: AccAddress

static id = `${command.id}:multisig`

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

this.command = new command(flags, args)

if (!AccAddress.validate(process.env.MULTISIG_ADDRESS)) throw new Error(`Invalid Multisig wallet address`)
this.multisig = process.env.MULTISIG_ADDRESS as AccAddress
}

makeRawTransaction = async (signer: AccAddress, status?: Action) => {
const message = await this.command.makeRawTransaction(this.multisig)

const operations = {
[Action.CREATE]: this.executePropose,
[Action.APPROVE]: this.executeApproval,
[Action.EXECUTE]: this.executeExecution,
}

return operations[status](signer, Number(this.flags.proposal), message)
}

toWasmMsg = (message: MsgExecuteContract): WasmMsg => {
return {
execute: {
contract_addr: message.contract,
funds: message.coins.toArray().map((c) => c.toData()),
msg: Buffer.from(message.toJSON()).toString('base64'),
},
}
}

executePropose: ProposalAction = async (signer, proposalId, message) => {
logger.info('Generating data for creating new proposal')
const proposeInput = {
propose: {
description: command.id,
msgs: [
{
wasm: this.toWasmMsg(message),
},
],
title: command.id,
// latest: {
// never: {},
// },
},
}
return new MsgExecuteContract(signer, this.multisig, proposeInput)
}

executeApproval: ProposalAction = async (signer, proposalId) => {
logger.info(`Generating data for approving proposal ${proposalId}`)
const approvalInput = {
vote: {
vote: Vote.YES,
proposal_id: proposalId,
},
}
return new MsgExecuteContract(signer, this.multisig, approvalInput)
}

makeRawTransaction = async () => {
// TODO: Replace with Mulstig tx message
return {} as MsgExecuteContract
executeExecution: ProposalAction = async (signer, proposalId) => {
logger.info(`Generating data for executing proposal ${proposalId}`)
const executeInput = {
execute: {
proposal_id: proposalId,
},
}
return new MsgExecuteContract(signer, this.multisig, executeInput)
}

fetchState = async (proposalId?: number): Promise<State> => {
const threshold = await this.query(this.multisig, {
threshold: {},
})
if (!proposalId)
return {
threshold,
proposalStatus: Action.CREATE,
}
const proposalState = await this.query(this.multisig, {
proposal: {
proposal_id: proposalId,
},
})
console.log(proposalState)

return {
threshold,
proposalStatus: Action.APPROVE,
}
}

execute = async () => {
let proposalId = !!this.flags.proposal && Number(this.flags.proposal)
const state = await this.fetchState(proposalId)
const rawTx = await this.makeRawTransaction(this.wallet.key.accAddress, state.proposalStatus)

console.info(`
Proposal State:
- Threshold: ${state.threshold}
- Status: ${state.proposalStatus}
`)

const actionMessage = {
[Action.CREATE]: 'CREATING',
[Action.APPROVE]: 'APPROVING',
[Action.EXECUTE]: 'EXECUTING',
}
await prompt(`Continue ${actionMessage[state.proposalStatus]} proposal?`)
const tx = await this.signAndSend([rawTx])

if (state.proposalStatus === Action.CREATE) {
// get proposal ID from logs
}

// If ID Proposal is provided, check the proposal status, and either approve or execute.
// If ID Proposal is not provided, create a new proposal
const message = await this.command.makeRawTransaction()
logger.log('Command data:', message)

return {} as Result<TransactionResponse>
}
Expand Down
23 changes: 21 additions & 2 deletions packages-ts/gauntlet-terra/src/commands/internal/terra.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Result, WriteCommand } from '@chainlink/gauntlet-core'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
import { EventsByType, MsgStoreCode, TxLog } from '@terra-money/terra.js'
import { EventsByType, MsgStoreCode, AccAddress, TxLog } from '@terra-money/terra.js'
import { SignMode } from '@terra-money/terra.proto/cosmos/tx/signing/v1beta1/signing'

import { withProvider, withWallet, withCodeIds, withNetwork } from '../middlewares'
Expand All @@ -23,7 +23,7 @@ export default abstract class TerraCommand extends WriteCommand<TransactionRespo
contracts: string[]
public codeIds: CodeIds
abstract execute: () => Promise<Result<TransactionResponse>>
abstract makeRawTransaction: () => Promise<MsgExecuteContract>
abstract makeRawTransaction: (signer: AccAddress) => Promise<MsgExecuteContract>

constructor(flags, args) {
super(flags, args)
Expand Down Expand Up @@ -64,6 +64,25 @@ export default abstract class TerraCommand extends WriteCommand<TransactionRespo
return await this.provider.wasm.contractQuery(address, input, params)
}

signAndSend = async (messages: MsgExecuteContract[]): Promise<TransactionResponse> => {
try {
const tx = await this.wallet.createAndSignTx({
msgs: messages,
...(this.wallet.key instanceof LedgerKey && {
signMode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON,
}),
})

const res = await this.provider.tx.broadcast(tx)

logger.debug(res)
return this.wrapResponse(res)
} catch (e) {
const details = e.response.data
throw new Error(details.message)
}
}

async call(address, input) {
const msg = new MsgExecuteContract(this.wallet.key.accAddress, address, input)

Expand Down

0 comments on commit 1b0aa71

Please sign in to comment.