-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multisig wrapper #116
Multisig wrapper #116
Changes from all commits
1819512
201ab1f
c27ce1c
f97ec19
f7891ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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 }, []) | ||
} | ||
} | ||
|
||
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 ${slug} not found`) | ||
} | ||
const op: string = slugs.pop()! | ||
const instruction = slugs.join(':') | ||
|
||
const commandType = commands[instruction] ? commands[instruction] : AbstractCommand | ||
|
||
switch (op) { | ||
case 'multisig': | ||
case 'propose': | ||
case 'vote': | ||
case 'execute': | ||
case 'approve': // vote yes, then execute if threshold is reached | ||
class WrappedCommand extends MultisigTerraCommand { | ||
static id = instruction | ||
static commandType = commandType | ||
|
||
constructor(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 | ||
} | ||
} | ||
|
||
// This is a temporary workaround for a bug in the type specification for findPolymorphic in @gauntlet-core. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Polymorphic is a legacy command that gauntlet-core had to support for old Gauntlet EVM, but we won't be using it in the future |
||
// 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`) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
import { CreateGroup } from './group' | ||
import { CreateWallet } from './wallet' | ||
import { MultisigTerraCommand } from './multisig' | ||
|
||
export default [CreateGroup, CreateWallet] | ||
export { MultisigTerraCommand } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// multisig.ts | ||
// | ||
// For now, propose, vote, and execute functionality are all combined into one CONTRACT:COMMAND::multisig meta-command | ||
// This is parallel to how things are implemented in Solana. The execute happens automatically as soon as the last | ||
// vote required to exceeed the threshold is cast. And the difference between propose and vote is distinguished by | ||
// whether the --proposal=PROPOSAL_HASH flag is passed. Later, we may want to split this into CONTRACT::COMMAND::propose, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to stick to the same process as EVM and Solana, where the multisig command tells us which step we are based on the proposal state |
||
// CONTRACT::COMMAND::vote, and CONTRACT::COMMAND::execute. We may also want to add CONTRACT::COMMAND::close, to | ||
// abort a proposal early (before it expires), disallowing any further voting on it. | ||
|
||
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' | ||
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 StringGetter = () => string | ||
export type ICommandConstructor = (flags: any, args: string[]) => void | ||
|
||
abstract class MultisigTerraCommand extends TerraCommand { | ||
static category = CATEGORIES.MULTISIG | ||
|
||
commandType: typeof TerraCommand | ||
multisigOp: StringGetter | ||
|
||
command: TerraCommand | ||
multisigAddress: string | ||
multisigContract: Promise<Contract> | ||
|
||
constructor(flags, args) { | ||
super(flags, args) | ||
} | ||
|
||
postConstructor(flags, args) { | ||
// Called after child constructor | ||
logger.debug(`Running ${this.commandType} in multisig mode`) | ||
|
||
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<Result<TransactionResponse>> => { | ||
const tx = this.command.makeRawTransaction() | ||
console.debug(tx) | ||
|
||
return { | ||
responses: [], | ||
} as Result<TransactionResponse> | ||
} | ||
} | ||
|
||
export const wrapCommand = (command) => { | ||
return class CustomCommand extends MultisigTerraCommand { | ||
static id = `${command.id}:multisig` | ||
static category = CATEGORIES.MULTISIG | ||
} | ||
} | ||
|
||
export { MultisigTerraCommand } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import TerraCommand from './commands/internal/terra' | ||
import { waitExecute } from './lib/execute' | ||
import { TransactionResponse } from './commands/types' | ||
import { RawTransaction, TransactionResponse } from './commands/types' | ||
import * as constants from './lib/constants' | ||
|
||
export { TerraCommand, waitExecute, TransactionResponse, constants } | ||
export { RawTransaction, TerraCommand, waitExecute, TransactionResponse, constants } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this "polymorphic" command? Why do we need it? We are already wrapping the commands into a multisig command