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 cf10e473..71a41dbf 100644 --- a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts +++ b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/index.ts @@ -1,6 +1,6 @@ import { Result } from '@chainlink/gauntlet-core' import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' -import { TransactionResponse, TerraCommand } from '@chainlink/gauntlet-terra' +import { TransactionResponse, TerraCommand, RawTransaction } from '@chainlink/gauntlet-terra' import { Contract, CONTRACT_LIST, getContract, TerraABI, TERRA_OPERATIONS } from '../../lib/contracts' import { DEFAULT_RELEASE_VERSION } from '../../lib/constants' import schema from '../../lib/schema' @@ -185,28 +185,30 @@ export default class AbstractCommand extends TerraCommand { } // create and sign transaction, without executing - makeRawTransaction = async () => { + makeRawTransaction = async (): Promise => { const operations = { [TERRA_OPERATIONS.DEPLOY]: this.abstractPrepareDeploy, [TERRA_OPERATIONS.EXECUTE]: this.abstractPrepareCall, - [TERRA_OPERATIONS.QUERY]: () => { throw Error("makeRawTransaction: cannot make a tx from a query commmand") }, + [TERRA_OPERATIONS.QUERY]: () => { + throw Error('makeRawTransaction: cannot make a tx from a query commmand') + }, // TODO: [TERRA_OPERATIONS.UPLOAD]: this.abstractPrepareUpload, } return await operations[this.opts.action](this.params, this.args[0]) -} + } -abstractPrepareDeploy = async(params:any) => { - const codeId = this.codeIds[this.opts.contract.id] - this.require(!!codeId, `Code Id for contract ${this.opts.contract.id} not found`) - return await this.prepareDeploy(codeId, params) -} + abstractPrepareDeploy = async (params: any) => { + const codeId = this.codeIds[this.opts.contract.id] + this.require(!!codeId, `Code Id for contract ${this.opts.contract.id} not found`) + return await this.prepareDeploy(codeId, params) + } -abstractPrepareCall = async(params:any, address:string) => { - return await this.prepareCall(address, { - [this.opts.function]: params, - }) -} + abstractPrepareCall = async (params: any, address: string) => { + return await this.prepareCall(address, { + [this.opts.function]: params, + }) + } execute = async () => { const operations = { diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/polymorphic.ts b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/polymorphic.ts index 5266625b..37cde568 100644 --- a/packages-ts/gauntlet-terra-contracts/src/commands/abstract/polymorphic.ts +++ b/packages-ts/gauntlet-terra-contracts/src/commands/abstract/polymorphic.ts @@ -1,21 +1,28 @@ import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra' import { MultisigTerraCommand } from '../contracts/multisig' import { Result, Command } from '@chainlink/gauntlet-core' +import { logger } from '@chainlink/gauntlet-core/dist/utils' +import DeployLink from '../../commands/contracts/link/deploy' +import AbstractCommand from '.' +import { makeAbstractCommand } from '.' class EmptyCommand extends Command { - constructor() { - super({help : false}, []) - } + constructor() { + super({ help: false }, []) + } } -export default (commands: any[], slug: string) : Command => { +type ICommandConstructor = (flags: any, args: string[]) => void + +export default (commands: any, slug: string): Command => { const slugs: string[] = slug.split(':') if (slugs.length < 3) { - throw Error(`Command ${slugs.join(':')} not found`) + throw Error(`Command ${slug} not found`) } const op: string = slugs.pop()! - const command: any = commands[slugs.join()] - if (!!command) throw Error(`Command ${slugs.join(':')} not found`) + const instruction = slugs.join(':') + + const commandType = commands[instruction] ? commands[instruction] : AbstractCommand switch (op) { case 'multisig': @@ -24,25 +31,34 @@ export default (commands: any[], slug: string) : Command => { case 'execute': case 'approve': // vote yes, then execute if threshold is reached class WrappedCommand extends MultisigTerraCommand { - static id = slugs.join() + static id = instruction + static commandType = commandType constructor(flags, args) { - super(flags, args) + super(flags, args) + if (commandType === AbstractCommand) { + throw Error(`Command ${instruction} not found`) + // TODO: get this working for abstract commands. Something like: + // this.command = await makeAbstractCommand(instruction, flags, args) + } else { + this.command = new commandType(flags, args) + } } multisigOp = () => { return op } - commandType = () => { - return command - } } - let cmd = new EmptyCommand() - let wc = Object.setPrototypeOf(WrappedCommand, EmptyCommand.prototype) - wc = Object.assign(wc, cmd) - return wc + // This is a temporary workaround for a bug in the type specification for findPolymorphic in @gauntlet-core. + // At runtime, it's used as a constructor. It must be callable and able to construct an actual command object + // when passed flags & args, (ie, it should be a class). But it's declared as if it were an instance of a type + // satisfying ICommand, which will cause typescript to reject it during compilation if it's a class satisfying + // the ICommand interface. The only way to satisfy both constraints is for it to look like an instance at compile + // time, and a class at runtime. The workaround is to return something that is both at once: add all properties + // of an instance of EmtpyCommand to the class WrappedCommand and return the resulting hybrid. + return Object.assign(WrappedCommand, new EmptyCommand()) default: - throw Error(`Command ${slugs.join(':')} not found`) + throw Error(`Command ${slugs.join(':')} not found`) } } diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/deploy.ts b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/deploy.ts index c644a4e9..49f5e2df 100644 --- a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/deploy.ts +++ b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/link/deploy.ts @@ -1,4 +1,4 @@ -import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra' +import { TerraCommand, TransactionResponse, RawTransaction } from '@chainlink/gauntlet-terra' import { Result } from '@chainlink/gauntlet-core' import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' import { CATEGORIES, CW20_BASE_CODE_IDs } from '../../../lib/constants' @@ -19,9 +19,8 @@ export default class DeployLink extends TerraCommand { super(flags, args) } - execute = async () => { - await prompt(`Begin deploying LINK Token?`) - const deploy = await this.deploy(CW20_BASE_CODE_IDs[this.flags.network], { + genParams = () => { + return { name: 'ChainLink Token', symbol: 'LINK', decimals: 18, @@ -36,7 +35,18 @@ export default class DeployLink extends TerraCommand { mint: { minter: this.wallet.key.accAddress, }, - }) + } + } + + makeRawTransaction = async (): Promise => { + const codeId = CW20_BASE_CODE_IDs[this.flags.network] + this.require(!!codeId, `Code Id for link token contract not found`) + return await this.prepareDeploy(CW20_BASE_CODE_IDs[this.flags.network], this.genParams()) + } + + execute = async () => { + await prompt(`Begin deploying LINK Token?`) + const deploy = await this.deploy(CW20_BASE_CODE_IDs[this.flags.network], this.genParams()) const result = await this.provider.wasm.contractQuery(deploy.address!, { token_info: {} }) logger.success(`LINK token successfully deployed at ${deploy.address} (txhash: ${deploy.hash})`) logger.debug(result) diff --git a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/multisig/multisig.ts b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/multisig/multisig.ts index 4b3f246f..96eed9cb 100644 --- a/packages-ts/gauntlet-terra-contracts/src/commands/contracts/multisig/multisig.ts +++ b/packages-ts/gauntlet-terra-contracts/src/commands/contracts/multisig/multisig.ts @@ -8,55 +8,59 @@ // abort a proposal early (before it expires), disallowing any further voting on it. import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' -import { Result } from '@chainlink/gauntlet-core' +import { Result, ICommand } from '@chainlink/gauntlet-core' import { TerraCommand, RawTransaction, TransactionResponse } from '@chainlink/gauntlet-terra' import { CATEGORIES } from '../../../lib/constants' import { CONTRACT_LIST, Contract, getContract, TERRA_OPERATIONS } from '../../../lib/contracts' import AbstractCommand from '../../abstract' -type ProposalContext = { - rawTx: RawTransaction, - multisigSigner: string, - proposalState: any, +type ProposalContext = { + rawTx: RawTransaction + multisigSigner: string + proposalState: any } type StringGetter = () => string +export type ICommandConstructor = (flags: any, args: string[]) => void -abstract class MultisigTerraCommand extends TerraCommand { - static category = CATEGORIES.MULTISIG +abstract class MultisigTerraCommand extends TerraCommand { + static category = CATEGORIES.MULTISIG + + commandType: typeof TerraCommand + multisigOp: StringGetter + + command: TerraCommand + multisigAddress: string + multisigContract: Promise + + constructor(flags, args) { + super(flags, args) + } + + postConstructor(flags, args) { + // Called after child constructor + logger.debug(`Running ${this.commandType} in multisig mode`) - commandType:any - multisigOp:StringGetter - - command:AbstractCommand - multisigAddress:string - multisigContract: Promise - - constructor(flags, args) { - super(flags, args) - - logger.info(`Running ${this.commandType()} in multisig mode`) - this.command = new this.commandType()(flags, args) - this.command.invokeMiddlewares(this.command, this.command.middlewares) - this.require(!!process.env.MULTISIG_ADDRESS, 'Please set MULTISIG_ADDRESS env var') - this.multisigContract = getContract(CONTRACT_LIST.MULTISIG, flags.version) - this.multisigAddress = process.env.MULTISIG_ADDRESS! - } - - execute = async (): Promise> => { - const tx = this.command.makeRawTransaction() - console.debug(tx) - - return { - responses: [], - } as Result - } + this.command.invokeMiddlewares(this.command, this.command.middlewares) + this.require(!!process.env.MULTISIG_WALLET, 'Please set MULTISIG_WALLET env var') + this.multisigContract = getContract(CONTRACT_LIST.MULTISIG, flags.version) + this.multisigAddress = process.env.MULTISIG_WALLET! + } + + execute = async (): Promise> => { + const tx = this.command.makeRawTransaction() + console.debug(tx) + + return { + responses: [], + } as Result + } } export const wrapCommand = (command) => { - return class CustomCommand extends MultisigTerraCommand { - static id = `${command.id}:multisig` - static category = CATEGORIES.MULTISIG + return class CustomCommand extends MultisigTerraCommand { + static id = `${command.id}:multisig` + static category = CATEGORIES.MULTISIG } } diff --git a/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts b/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts index 20cb82fe..7f803cd2 100644 --- a/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts +++ b/packages-ts/gauntlet-terra-contracts/src/lib/constants.ts @@ -21,15 +21,3 @@ export const CW20_BASE_CODE_IDs = { local: 32, 'bombay-testnet': 148, } - -export const CW4_GROUP_CODE_IDs = { - mainnet: -1, - local: -1, - 'bombay-testnet': 36895, -} - -export const CW3_FLEX_MULTISIG_CODE_IDs = { - mainnet: -1, - local: -1, - 'bombay-testnet': 36059, -} diff --git a/packages-ts/gauntlet-terra/src/commands/internal/terra.ts b/packages-ts/gauntlet-terra/src/commands/internal/terra.ts index 0057c2eb..67b1210b 100644 --- a/packages-ts/gauntlet-terra/src/commands/internal/terra.ts +++ b/packages-ts/gauntlet-terra/src/commands/internal/terra.ts @@ -1,4 +1,5 @@ import { Result, WriteCommand } from '@chainlink/gauntlet-core' +import { RawTransaction } from '../types' import { logger } from '@chainlink/gauntlet-core/dist/utils' import { EventsByType, MsgStoreCode, TxLog } from '@terra-money/terra.js' import { SignMode } from '@terra-money/terra.proto/cosmos/tx/signing/v1beta1/signing' @@ -29,6 +30,10 @@ export default abstract class TerraCommand extends WriteCommand => { + throw Error('makeRawTransaction: not implemented!') + } + parseResponseValue(receipt: any, eventType: string, attributeType: string) { try { const parsed = JSON.parse(receipt?.raw_log)