Skip to content

Commit

Permalink
transfer token and send uluna commands
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoAD committed Feb 28, 2022
1 parent c5677a4 commit 07439d2
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -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<Input, ContractInput> {
examples?: string[]
instruction: {
Expand All @@ -13,46 +20,66 @@ export interface AbstractInstruction<Input, ContractInput> {
makeInput: (flags: any, args: string[]) => Promise<Input>
validateInput: (input: Input) => boolean
makeContractInput: (input: Input) => Promise<ContractInput>
beforeExecute?: (context: BeforeExecutionContext) => Promise<void>
afterExecute?: (response: Result<TransactionResponse>) => any
}

export const instructionToCommand = (instruction: AbstractInstruction<any, any>) => {
export const defaultAfterExecute = async (response: Result<TransactionResponse>): Promise<void> => {
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 = <Input, ContractInput>(instruction: AbstractInstruction<Input, ContractInput>) => {
const id = `${instruction.instruction.contract}:${instruction.instruction.function}`
const category = `${instruction.instruction.category}`
const examples = instruction.examples || []
return class Command extends TerraCommand {
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<TerraCommand> => {
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<MsgExecuteContract> => {
makeRawTransaction = async (signer: AccAddress) => {
const command = await this.buildCommand()
return command.makeRawTransaction(signer)
}

execute = async (): Promise<Result<TransactionResponse>> => {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CommandInput> => {
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<TransactionResponse>
const makeContractInput = async (input: CommandInput): Promise<ContractInput> => {
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<void> => {
logger.info(
`Transferring ${context.contractInput.amount} (${context.input.amount}) Tokens to ${context.contractInput.recipient}`,
)
await prompt('Continue?')
}

const transferToken: AbstractInstruction<CommandInput, ContractInput> = {
instruction: {
category: CATEGORIES.LINK,
contract: 'cw20_base',
function: 'transfer',
},
makeInput: makeCommandInput,
validateInput: validateInput,
makeContractInput: makeContractInput,
beforeExecute,
}

export default instructionToCommand(transferToken)
2 changes: 2 additions & 0 deletions packages-ts/gauntlet-terra-contracts/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,4 +21,5 @@ export default [
...Proxy_OCR2,
...Multisig,
...CW4_GROUP,
...Wallet,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Send from './send'

export default [Send]
60 changes: 60 additions & 0 deletions packages-ts/gauntlet-terra-contracts/src/commands/wallet/send.ts
Original file line number Diff line number Diff line change
@@ -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<TransactionResponse>
await this.afterExecute(result)
return result
}
}
3 changes: 3 additions & 0 deletions packages-ts/gauntlet-terra-contracts/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
30 changes: 24 additions & 6 deletions packages-ts/gauntlet-terra-cw-plus/src/commands/multisig.ts
Original file line number Diff line number Diff line change
@@ -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<MsgExecuteContract>

export const wrapCommand = (command) => {
Expand Down Expand Up @@ -43,18 +43,34 @@ 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',
)
}

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: {
Expand All @@ -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 },
Expand Down Expand Up @@ -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<TransactionResponse> = {
Expand Down
Loading

0 comments on commit 07439d2

Please sign in to comment.