Skip to content

Commit

Permalink
multisig command improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoAD committed Feb 16, 2022
1 parent 1b0aa71 commit e3e6932
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 36 deletions.
134 changes: 98 additions & 36 deletions packages-ts/gauntlet-terra-cw20-multisig/src/commands/multisig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
import { AccAddress, MsgExecuteContract } from '@terra-money/terra.js'
import { isDeepEqual } from '../lib/utils'

type ProposalAction = (
signer: AccAddress,
Expand All @@ -17,25 +18,31 @@ enum Vote {
}

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

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

type State = {
threshold: number
proposalStatus: Action
nextAction: Action
// https://github.com/CosmWasm/cw-plus/blob/82138f9484e538913f7faf78bc292fb14407aae8/packages/cw3/src/query.rs#L75
currentStatus?: 'pending' | 'open' | 'rejected' | 'passed' | 'executed'
data?: WasmMsg[]
}

export const wrapCommand = (command) => {
Expand All @@ -54,7 +61,7 @@ export const wrapCommand = (command) => {
this.multisig = process.env.MULTISIG_ADDRESS as AccAddress
}

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

const operations = {
Expand All @@ -63,33 +70,41 @@ export const wrapCommand = (command) => {
[Action.EXECUTE]: this.executeExecution,
}

return operations[status](signer, Number(this.flags.proposal), message)
if (state.nextAction !== Action.CREATE) {
this.require(
await this.isSameProposal(state.data, [this.toWasmMsg(message)]),
'The transaction generated is different from the proposal provided',
)
}

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

isSameProposal = (proposalMsgs: WasmMsg[], generatedMsgs: WasmMsg[]) => {
return isDeepEqual(proposalMsgs, generatedMsgs)
}

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'),
wasm: {
execute: {
contract_addr: message.contract,
funds: message.coins.toArray().map((c) => c.toData()),
msg: Buffer.from(JSON.stringify(message.execute_msg)).toString('base64'),
},
},
}
}

executePropose: ProposalAction = async (signer, proposalId, message) => {
executePropose: ProposalAction = async (signer, _, message) => {
logger.info('Generating data for creating new proposal')
const proposeInput = {
propose: {
description: command.id,
msgs: [
{
wasm: this.toWasmMsg(message),
},
],
msgs: [this.toWasmMsg(message)],
title: command.id,
// latest: {
// never: {},
// },
// TODO: Set expiration time
// latest: { never: {} }
},
}
return new MsgExecuteContract(signer, this.multisig, proposeInput)
Expand Down Expand Up @@ -117,54 +132,101 @@ export const wrapCommand = (command) => {
}

fetchState = async (proposalId?: number): Promise<State> => {
const threshold = await this.query(this.multisig, {
const thresholdState = await this.query(this.multisig, {
threshold: {},
})
if (!proposalId)
const threshold = thresholdState.absolute_count.total_weight
if (!proposalId) {
return {
threshold,
proposalStatus: Action.CREATE,
nextAction: Action.CREATE,
}
}

const proposalState = await this.query(this.multisig, {
proposal: {
proposal_id: proposalId,
},
})
console.log(proposalState)

// TODO: Add owners

const status = proposalState.status
const toNextAction = {
passed: Action.EXECUTE,
open: Action.APPROVE,
pending: Action.APPROVE,
rejected: Action.NONE,
executed: Action.NONE,
}
return {
threshold,
proposalStatus: Action.APPROVE,
nextAction: toNextAction[status],
currentStatus: status,
data: proposalState.msgs,
}
}

printPostInstructions = async (proposalId: number) => {
const state = await this.fetchState(proposalId)
// TODO: Calculate approvals left
const approvalsLeft = state.threshold - 1
const messages = {
[Action.APPROVE]: `The proposal needs ${approvalsLeft} more approvals. Run the same command with the flag --proposal=${proposalId}`,
[Action.EXECUTE]: `The proposal reached the threshold and can be executed. Run the same command with the flag --proposal=${proposalId}`,
[Action.NONE]: `The proposal has been executed. No more actions needed`,
}
logger.line()
logger.info(`Next Actions:
${messages[state.nextAction]}
`)
logger.line()
}

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)

if (state.nextAction === Action.NONE) {
logger.info('The proposal has already been executed/rejected')
return
}
const rawTx = await this.makeRawTransaction(this.wallet.key.accAddress, state)

console.info(`
Proposal State:
- Threshold: ${state.threshold}
- Status: ${state.proposalStatus}
- Next Action: ${state.nextAction.toUpperCase()}
- Owners: TODO
`)

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

if (state.proposalStatus === Action.CREATE) {
// get proposal ID from logs
if (state.nextAction === Action.CREATE) {
const proposalFromEvent = tx.events[0].wasm.proposal_id[0]
logger.success(`New proposal created with ID: ${proposalFromEvent}`)
proposalId = Number(proposalFromEvent)
}

// If ID Proposal is provided, check the proposal status, and either approve or execute.
// If ID Proposal is not provided, create a new proposal
await this.printPostInstructions(proposalId)

return {} as Result<TransactionResponse>
return {
responses: [
{
tx,
contract: this.multisig,
},
],
data: {
proposalId,
},
} as Result<TransactionResponse>
}
}
}
13 changes: 13 additions & 0 deletions packages-ts/gauntlet-terra-cw20-multisig/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import assert from 'assert'

export const isDeepEqual = (a: any, b: any) => {
try {
assert.deepStrictEqual(a, b)
} catch (error) {
if (error.name === 'AssertionError') {
return false
}
throw error
}
return true
}

0 comments on commit e3e6932

Please sign in to comment.