From 07439d2d3c8f87287095f54a0585c00618ea0cfd Mon Sep 17 00:00:00 2001
From: RodrigoAD <15104916+RodrigoAD@users.noreply.github.com>
Date: Mon, 28 Feb 2022 15:00:43 +0100
Subject: [PATCH] transfer token and send uluna commands
---
.../src/commands/abstract/executionWrapper.ts | 53 +++++++++---
.../src/commands/abstract/index.ts | 6 +-
.../src/commands/contracts/link/transfer.ts | 83 +++++++++++--------
.../src/commands/index.ts | 2 +
.../src/commands/wallet/index.ts | 3 +
.../src/commands/wallet/send.ts | 60 ++++++++++++++
.../src/lib/constants.ts | 3 +
.../src/commands/multisig.ts | 30 +++++--
.../gauntlet-terra-cw-plus/src/lib/types.ts | 19 ++++-
.../src/commands/internal/terra.ts | 7 +-
10 files changed, 200 insertions(+), 66 deletions(-)
create mode 100644 packages-ts/gauntlet-terra-contracts/src/commands/wallet/index.ts
create mode 100644 packages-ts/gauntlet-terra-contracts/src/commands/wallet/send.ts
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/executionWrapper.ts b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/executionWrapper.ts
index 9919fe9c2..469506b86 100644
--- a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/executionWrapper.ts
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/executionWrapper.ts
@@ -1,8 +1,15 @@
-import AbstractCommand, { makeAbstractCommand } from '.'
+import { makeAbstractCommand } from '.'
import { Result } from '@chainlink/gauntlet-core'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
-import { AccAddress, MsgExecuteContract } from '@terra-money/terra.js'
+import { AccAddress } from '@terra-money/terra.js'
+import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
+export type BeforeExecutionContext = {
+ input: any
+ contractInput: any
+ commandId: string
+ contract: string
+}
export interface AbstractInstruction {
examples?: string[]
instruction: {
@@ -13,10 +20,21 @@ export interface AbstractInstruction {
makeInput: (flags: any, args: string[]) => Promise
validateInput: (input: Input) => boolean
makeContractInput: (input: Input) => Promise
+ beforeExecute?: (context: BeforeExecutionContext) => Promise
afterExecute?: (response: Result) => any
}
-export const instructionToCommand = (instruction: AbstractInstruction) => {
+export const defaultAfterExecute = async (response: Result): Promise => {
+ logger.success(`Execution finished at transaction: ${response.responses[0].tx.hash}`)
+}
+
+export const defaultBeforeExecute = async (context: BeforeExecutionContext) => {
+ logger.loading(`Executing ${context.commandId} from contract ${context.contract}`)
+ logger.log('Input Params:', context.contractInput)
+ await prompt(`Continue?`)
+}
+
+export const instructionToCommand = (instruction: AbstractInstruction) => {
const id = `${instruction.instruction.contract}:${instruction.instruction.function}`
const category = `${instruction.instruction.category}`
const examples = instruction.examples || []
@@ -24,35 +42,44 @@ export const instructionToCommand = (instruction: AbstractInstruction)
static id = id
static category = category
static examples = examples
- command: AbstractCommand
+
+ input: Input
+ contractInput: ContractInput
constructor(flags, args) {
super(flags, args)
}
- afterExecute = instruction.afterExecute
+ beforeExecute = instruction.beforeExecute || defaultBeforeExecute
+ afterExecute = instruction.afterExecute || defaultAfterExecute
buildCommand = async (): Promise => {
- const commandInput = await instruction.makeInput(this.flags, this.args)
- if (!instruction.validateInput(commandInput)) {
- throw new Error(`Invalid input params: ${JSON.stringify(commandInput)}`)
+ this.input = await instruction.makeInput(this.flags, this.args)
+ if (!instruction.validateInput(this.input)) {
+ throw new Error(`Invalid input params: ${JSON.stringify(this.input)}`)
}
- const input = await instruction.makeContractInput(commandInput)
- const abstractCommand = await makeAbstractCommand(id, this.flags, this.args, input)
+ this.contractInput = await instruction.makeContractInput(this.input)
+ const abstractCommand = await makeAbstractCommand(id, this.flags, this.args, this.contractInput)
await abstractCommand.invokeMiddlewares(abstractCommand, abstractCommand.middlewares)
return abstractCommand
}
- makeRawTransaction = async (signer: AccAddress): Promise => {
+ makeRawTransaction = async (signer: AccAddress) => {
const command = await this.buildCommand()
return command.makeRawTransaction(signer)
}
execute = async (): Promise> => {
const command = await this.buildCommand()
+ await this.beforeExecute({
+ input: this.input,
+ contractInput: this.contractInput,
+ commandId: id,
+ contract: this.args[0],
+ })
let response = await command.execute()
- if (this.afterExecute) {
- const data = this.afterExecute(response)
+ const data = this.afterExecute(response)
+ if (data) {
response = { ...response, data: { ...data } }
}
return response
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts
index bbf268635..b8233a766 100644
--- a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts
@@ -153,11 +153,9 @@ export default class AbstractCommand extends TerraCommand {
}
abstractExecute: AbstractExecute = async (params: any, address: string) => {
- logger.loading(`Executing ${this.opts.function} from contract ${this.opts.contract.id} at ${address}`)
- logger.log('Input Params:', params)
- await prompt(`Continue?`)
+ logger.debug(`Executing ${this.opts.function} from contract ${this.opts.contract.id} at ${address}`)
const tx = await this.call(address, params)
- logger.success(`Execution finished at tx ${tx.hash}`)
+ logger.debug(`Execution finished at tx ${tx.hash}`)
return {
responses: [
{
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/transfer.ts b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/transfer.ts
index 85f23e6df..39a47e926 100644
--- a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/transfer.ts
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/transfer.ts
@@ -1,46 +1,57 @@
-import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
-import { Result } from '@chainlink/gauntlet-core'
import { BN, logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { CATEGORIES, TOKEN_DECIMALS } from '../../../lib/constants'
+import { AbstractInstruction, BeforeExecutionContext, instructionToCommand } from '../../abstract/executionWrapper'
+import { AccAddress } from '@terra-money/terra.js'
-export default class TransferLink extends TerraCommand {
- static description = 'Transfer LINK'
- static examples = [
- `yarn gauntlet token:transfer --network=bombay-testnet --to=[RECEIVER] --amount=[AMOUNT_IN_TOKEN_UNITS]`,
- `yarn gauntlet token:transfer --network=bombay-testnet --to=[RECEIVER] --amount=[AMOUNT_IN_TOKEN_UNITS] --link=[TOKEN_ADDRESS] --decimals=[TOKEN_DECIMALS]`,
- ]
+type CommandInput = {
+ to: string
+ // Units in LINK
+ amount: string
+}
- static id = 'token:transfer'
- static category = CATEGORIES.LINK
+type ContractInput = {
+ recipient: string
+ amount: string
+}
- constructor(flags, args: string[]) {
- super(flags, args)
+const makeCommandInput = async (flags: any): Promise => {
+ if (flags.input) return flags.input as CommandInput
+ return {
+ to: flags.to,
+ amount: flags.amount,
}
+}
- makeRawTransaction = async () => {
- throw new Error('Transfer LINK command: makeRawTransaction method not implemented')
- }
+const validateInput = (input: CommandInput): boolean => {
+ if (!AccAddress.validate(input.to)) throw new Error(`Invalid destination address`)
+ return true
+}
- execute = async () => {
- const decimals = this.flags.decimals || TOKEN_DECIMALS
- const link = this.flags.link || process.env.LINK
- const amount = new BN(this.flags.amount).mul(new BN(10).pow(new BN(decimals)))
-
- await prompt(`Sending ${this.flags.amount} LINK (${amount.toString()}) to ${this.flags.to}. Continue?`)
- const tx = await this.call(link, {
- transfer: {
- recipient: this.flags.to,
- amount: amount.toString(),
- },
- })
- logger.success(`LINK transferred successfully to ${this.flags.to} (txhash: ${tx.hash})`)
- return {
- responses: [
- {
- tx,
- contract: link,
- },
- ],
- } as Result
+const makeContractInput = async (input: CommandInput): Promise => {
+ const amount = new BN(input.amount).mul(new BN(10).pow(new BN(TOKEN_DECIMALS)))
+ return {
+ recipient: input.to,
+ amount: amount.toString(),
}
}
+
+const beforeExecute = async (context: BeforeExecutionContext): Promise => {
+ logger.info(
+ `Transferring ${context.contractInput.amount} (${context.input.amount}) Tokens to ${context.contractInput.recipient}`,
+ )
+ await prompt('Continue?')
+}
+
+const transferToken: AbstractInstruction = {
+ instruction: {
+ category: CATEGORIES.LINK,
+ contract: 'cw20_base',
+ function: 'transfer',
+ },
+ makeInput: makeCommandInput,
+ validateInput: validateInput,
+ makeContractInput: makeContractInput,
+ beforeExecute,
+}
+
+export default instructionToCommand(transferToken)
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/index.ts b/packages-ts/gauntlet-terra-contracts/src/commands/index.ts
index eea44285c..30f7e77fa 100644
--- a/packages-ts/gauntlet-terra-contracts/src/commands/index.ts
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/index.ts
@@ -8,6 +8,7 @@ import Proxy_OCR2 from './contracts/proxy_ocr2'
import DeviationFlaggingValidator from './contracts/deviation_flagging_validator'
import Multisig from './contracts/multisig'
import CW4_GROUP from './contracts/cw4_group'
+import Wallet from './wallet'
export default [
Upload,
@@ -20,4 +21,5 @@ export default [
...Proxy_OCR2,
...Multisig,
...CW4_GROUP,
+ ...Wallet,
]
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/wallet/index.ts b/packages-ts/gauntlet-terra-contracts/src/commands/wallet/index.ts
new file mode 100644
index 000000000..7400c704a
--- /dev/null
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/wallet/index.ts
@@ -0,0 +1,3 @@
+import Send from './send'
+
+export default [Send]
diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/wallet/send.ts b/packages-ts/gauntlet-terra-contracts/src/commands/wallet/send.ts
new file mode 100644
index 000000000..25d39129a
--- /dev/null
+++ b/packages-ts/gauntlet-terra-contracts/src/commands/wallet/send.ts
@@ -0,0 +1,60 @@
+import { BN, logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
+import { defaultAfterExecute } from '../abstract/executionWrapper'
+import { AccAddress, MsgSend } from '@terra-money/terra.js'
+import { CATEGORIES, ULUNA_DECIMALS } from '../../lib/constants'
+import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
+import { Result } from '@chainlink/gauntlet-core'
+
+type CommandInput = {
+ destination: string
+ // Units in LUNA
+ amount: string
+}
+
+export default class TransferLuna extends TerraCommand {
+ static description = 'Transfer Luna'
+ static examples = [`yarn gauntlet wallet:transfer --network=bombay-testnet`]
+
+ static id = 'wallet:transfer'
+ static category = CATEGORIES.WALLET
+
+ input: CommandInput
+
+ constructor(flags, args: string[]) {
+ super(flags, args)
+ }
+
+ beforeExecute = async (input: CommandInput) => {
+ await prompt(`Continue sending ${input.amount} uLUNA to ${input.destination}?`)
+ }
+ afterExecute = defaultAfterExecute
+
+ makeInput = () => {
+ return {
+ destination: this.flags.to,
+ amount: new BN(this.flags.amount).mul(new BN(10).pow(new BN(ULUNA_DECIMALS))),
+ }
+ }
+
+ makeRawTransaction = async (signer: AccAddress) => {
+ this.input = this.makeInput()
+ if (!AccAddress.validate(this.input.destination)) throw new Error('Invalid destination address')
+ return new MsgSend(signer, this.input.destination, `${this.input.amount.toString()}uluna`)
+ }
+
+ execute = async () => {
+ const message = await this.makeRawTransaction(this.wallet.key.accAddress)
+ await this.beforeExecute(this.input)
+ const tx = await this.signAndSend([message])
+ const result = {
+ responses: [
+ {
+ tx,
+ contract: '',
+ },
+ ],
+ } as Result
+ await this.afterExecute(result)
+ return result
+ }
+}
diff --git a/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts b/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts
index bddafa5e8..ee7c03bdf 100644
--- a/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts
+++ b/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts
@@ -11,6 +11,7 @@ export const enum CATEGORIES {
ACCESS_CONTROLLER = 'Access Controller',
MULTISIG = 'Multisig',
DEVIATION_FLAGGING_VALIDATOR = 'Devaiation Flagging Validator',
+ WALLET = 'Wallet',
}
export const DEFAULT_RELEASE_VERSION = 'local'
@@ -38,3 +39,5 @@ export const CW3_FLEX_MULTISIG_CODE_IDs = {
export const TOKEN_DECIMALS = 18
export const TOKEN_UNIT = new BN(10).pow(new BN(TOKEN_DECIMALS))
+
+export const ULUNA_DECIMALS = 6
diff --git a/packages-ts/gauntlet-terra-cw-plus/src/commands/multisig.ts b/packages-ts/gauntlet-terra-cw-plus/src/commands/multisig.ts
index 4063bedca..6db296f60 100644
--- a/packages-ts/gauntlet-terra-cw-plus/src/commands/multisig.ts
+++ b/packages-ts/gauntlet-terra-cw-plus/src/commands/multisig.ts
@@ -1,15 +1,15 @@
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 { AccAddress, MsgExecuteContract, MsgSend } from '@terra-money/terra.js'
import { isDeepEqual } from '../lib/utils'
-import { Vote, WasmMsg, Action, State } from '../lib/types'
+import { Vote, WasmMsg, Action, State, BankMsg } from '../lib/types'
import { fetchProposalState } from './inspect'
type ProposalAction = (
signer: AccAddress,
proposalId: number,
- message: MsgExecuteContract,
+ message: MsgExecuteContract | MsgSend,
) => Promise
export const wrapCommand = (command) => {
@@ -43,7 +43,7 @@ export const wrapCommand = (command) => {
if (state.nextAction !== Action.CREATE) {
this.require(
- await this.isSameProposal(state.data, [this.toWasmMsg(message)]),
+ await this.isSameProposal(state.data, [this.toMsg(message)]),
'The transaction generated is different from the proposal provided',
)
}
@@ -51,10 +51,26 @@ export const wrapCommand = (command) => {
return operations[state.nextAction](signer, Number(this.flags.proposal), message)
}
- isSameProposal = (proposalMsgs: WasmMsg[], generatedMsgs: WasmMsg[]) => {
+ isSameProposal = (proposalMsgs: (WasmMsg | BankMsg)[], generatedMsgs: (WasmMsg | BankMsg)[]) => {
return isDeepEqual(proposalMsgs, generatedMsgs)
}
+ toMsg = (message: MsgSend | MsgExecuteContract): BankMsg | WasmMsg => {
+ if (message instanceof MsgSend) return this.toBankMsg(message as MsgSend)
+ if (message instanceof MsgExecuteContract) return this.toWasmMsg(message as MsgExecuteContract)
+ }
+
+ toBankMsg = (message: MsgSend): BankMsg => {
+ return {
+ bank: {
+ send: {
+ amount: message.amount.toArray().map((c) => c.toData()),
+ to_address: message.to_address,
+ },
+ },
+ }
+ }
+
toWasmMsg = (message: MsgExecuteContract): WasmMsg => {
return {
wasm: {
@@ -72,7 +88,7 @@ export const wrapCommand = (command) => {
const proposeInput = {
propose: {
description: command.id,
- msgs: [this.toWasmMsg(message)],
+ msgs: [this.toMsg(message)],
title: command.id,
// TODO: Set expiration time
// latest: { at_height: 7970238 },
@@ -151,6 +167,8 @@ export const wrapCommand = (command) => {
}
if (this.flags.execute) {
+ // TODO: Improve beforeExecute
+ // if (this.command.beforeExecute) await this.command.beforeExecute()
await prompt(`Continue ${actionMessage[state.nextAction]} proposal?`)
const tx = await this.signAndSend([rawTx])
let response: Result = {
diff --git a/packages-ts/gauntlet-terra-cw-plus/src/lib/types.ts b/packages-ts/gauntlet-terra-cw-plus/src/lib/types.ts
index 15a9c8cc4..7eb36bf97 100644
--- a/packages-ts/gauntlet-terra-cw-plus/src/lib/types.ts
+++ b/packages-ts/gauntlet-terra-cw-plus/src/lib/types.ts
@@ -14,19 +14,30 @@ export enum Action {
NONE = 'none',
}
+type Coin = {
+ denom: string
+ amount: string
+}
+
export type WasmMsg = {
wasm: {
execute: {
contract_addr: string
- funds: {
- denom: string
- amount: string
- }[]
+ funds: Coin[]
msg: string
}
}
}
+export type BankMsg = {
+ bank: {
+ send: {
+ amount: Coin[]
+ to_address: string
+ }
+ }
+}
+
export type State = {
threshold: number
nextAction: Action
diff --git a/packages-ts/gauntlet-terra/src/commands/internal/terra.ts b/packages-ts/gauntlet-terra/src/commands/internal/terra.ts
index 4545bf38b..3b03bfa58 100644
--- a/packages-ts/gauntlet-terra/src/commands/internal/terra.ts
+++ b/packages-ts/gauntlet-terra/src/commands/internal/terra.ts
@@ -1,6 +1,6 @@
import { Result, WriteCommand } from '@chainlink/gauntlet-core'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
-import { EventsByType, MsgStoreCode, AccAddress, TxLog } from '@terra-money/terra.js'
+import { EventsByType, MsgStoreCode, AccAddress, TxLog, MsgSend } from '@terra-money/terra.js'
import { SignMode } from '@terra-money/terra.proto/cosmos/tx/signing/v1beta1/signing'
import { withProvider, withWallet, withCodeIds, withNetwork } from '../middlewares'
@@ -23,7 +23,8 @@ export default abstract class TerraCommand extends WriteCommand Promise>
- abstract makeRawTransaction: (signer: AccAddress) => Promise
+ abstract makeRawTransaction: (signer: AccAddress) => Promise
+ beforeExecute?: (context?) => Promise
afterExecute?: (response: Result) => any
constructor(flags, args) {
@@ -65,7 +66,7 @@ export default abstract class TerraCommand extends WriteCommand => {
+ signAndSend = async (messages: MsgExecuteContract[] | MsgSend[]): Promise => {
try {
logger.loading('Signing transaction...')
const tx = await this.wallet.createAndSignTx({