UNPKG

@idle-finance/hardhat-proposals-plugin

Version:
355 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AlphaProposalBuilder = exports.AlphaProposal = exports.AlphaProposalState = void 0; const ethers_1 = require("ethers"); const utils_1 = require("ethers/lib/utils"); const utils_2 = require("ethers/lib/utils"); const plugins_1 = require("hardhat/plugins"); const index_1 = require("../ethers-contracts/index"); const Timelock__factory_1 = require("../ethers-contracts/factories/Timelock__factory"); const constants_1 = require("../constants"); const proposal_1 = require("./proposal"); const util_1 = require("../util"); // ---------- Helper Functions ---------- function loadGovernor(contract, provider) { // If `contract` is already a type contract if (contract instanceof ethers_1.Contract) { return contract; } return index_1.GovernorAlpha__factory.connect(contract, provider); } function loadVotingToken(contract, provider) { // If `contract` is already a type contract if (contract instanceof ethers_1.Contract) { return contract; } return index_1.VotingToken__factory.connect(contract, provider); } // -------------------- Define proposal states -------------------- var AlphaProposalState; (function (AlphaProposalState) { AlphaProposalState[AlphaProposalState["PENDING"] = 0] = "PENDING"; AlphaProposalState[AlphaProposalState["ACTIVE"] = 1] = "ACTIVE"; AlphaProposalState[AlphaProposalState["CANCELED"] = 2] = "CANCELED"; AlphaProposalState[AlphaProposalState["DEFEATED"] = 3] = "DEFEATED"; AlphaProposalState[AlphaProposalState["SUCCEDED"] = 4] = "SUCCEDED"; AlphaProposalState[AlphaProposalState["QUEUED"] = 5] = "QUEUED"; AlphaProposalState[AlphaProposalState["EXPIRED"] = 6] = "EXPIRED"; AlphaProposalState[AlphaProposalState["EXECUTED"] = 7] = "EXECUTED"; })(AlphaProposalState = exports.AlphaProposalState || (exports.AlphaProposalState = {})); // -------------------- Define proposal -------------------- class AlphaProposal extends proposal_1.Proposal { constructor(hre, governor, votingToken) { super(hre); // call constructor on Proposal this.state = AlphaProposalState.PENDING; this.id = ethers_1.BigNumber.from("0"); this.description = ""; this.contracts = []; this.args = []; this.governor = governor ? loadGovernor(governor, this.getEthersProvider()) : undefined; this.votingToken = votingToken ? loadVotingToken(votingToken, this.getEthersProvider()) : undefined; } addAction(action) { super.addAction(action); this.contracts.push(action.contract); this.args.push(action.args); } setProposer(proposer) { this.proposer = proposer; } setGovernor(governor) { this.governor = loadGovernor(governor, this.getEthersProvider()); } setVotingToken(votingToken) { this.votingToken = loadVotingToken(votingToken, this.getEthersProvider()); } async propose(proposer) { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); proposer = proposer ? proposer : this.proposer; if (!proposer) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_PROPOSER); const governorAsProposer = this.governor.connect(proposer); const proposalId = await governorAsProposer .callStatic .propose(this.targets, this.values, this.signatures, this.calldatas, this.description); await governorAsProposer.propose(this.targets, this.values, this.signatures, this.calldatas, this.description); this.id = proposalId; this.markAsSubmitted(); } async loadProposal(data) { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (!this.votingToken) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_VOTING_TOKEN); let id = data; let proposal = new AlphaProposal(this.hre, this.governor, this.votingToken); proposal.markAsSubmitted(); proposal.id = ethers_1.BigNumber.from(id); let proposalInfo = await this.governor.proposals(id); let actionsInfo = await this.governor.getActions(id); proposal.proposer = new this.hre.ethers.VoidSigner(proposalInfo.proposer); proposal.targets = actionsInfo.targets; proposal.values = actionsInfo[1]; // `values` gets overwrittn by array.values proposal.signatures = actionsInfo.signatures; proposal.calldatas = actionsInfo.calldatas; proposal.description = "<DESCRIPTION NOT LOADED>"; let args = []; for (let i = 0; i < proposal.targets.length; i++) { proposal.contracts.push(null); // push null to contracts. const signature = proposal.signatures[i]; const calldata = proposal.calldatas[i]; const arg = utils_2.defaultAbiCoder.decode([signature], calldata); args.push(arg); } proposal.args = args; return proposal; } async getProposalState() { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (this.internalState != proposal_1.InternalProposalState.SUBMITTED) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.PROPOSAL_NOT_SUBMITTED); } const proposalState = await this.governor.state(this.id); return proposalState; } async vote(signer, support = true) { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (this.internalState != proposal_1.InternalProposalState.SUBMITTED) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.PROPOSAL_NOT_SUBMITTED); } let currentState = await this.getProposalState(); if (currentState == AlphaProposalState.ACTIVE) { await this.governor.connect(signer).castVote(this.id, support); } else { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, `Proposal is not in an active state, received ${currentState}`); } } async queue(signer) { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (this.internalState != proposal_1.InternalProposalState.SUBMITTED) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.PROPOSAL_NOT_SUBMITTED); } signer = signer ? signer : this.proposer; if (!signer) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_SIGNER); await this.governor.connect(signer).queue(this.id); } async execute(signer) { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (this.internalState != proposal_1.InternalProposalState.SUBMITTED) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.PROPOSAL_NOT_SUBMITTED); } signer = signer ? signer : this.proposer; if (!signer) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_SIGNER); await this.governor.connect(signer).execute(this.id); } /** * This method will simulate the proposal using the full on-chain process. * This may take significant time depending on the hardware you are using. * * @notice For this method to work the proposal must have a proposer with enough votes to reach quorem */ async _fullSimulate() { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (!this.votingToken) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_VOTING_TOKEN); if (!this.proposer) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_PROPOSER); let provider = this.getEthersProvider(); let proposerAddress = await this.proposer.getAddress(); let quoremVotes = await this.governor.quorumVotes(); let proposerVotes = await this.votingToken.getCurrentVotes(proposerAddress); if (proposerVotes < quoremVotes) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NOT_ENOUGH_VOTES); let timelock = Timelock__factory_1.Timelock__factory.connect(await this.governor.timelock(), provider); await this.propose(); let votingDelay = await (await this.governor.votingDelay()).add(1); await this.mineBlocks(votingDelay); await this.vote(this.proposer, true); let votingPeriod = await this.governor.votingPeriod(); await this.mineBlocks(votingPeriod); await this.queue(); let delay = await timelock.delay(); let blockInfo = await provider.getBlock("latest"); let endTimeStamp = delay.add(blockInfo.timestamp).add("50").toNumber(); await this.mineBlock(endTimeStamp); await this.execute(); } // queues the action to the timelock by impersonating the governor // advances time in order to execute proposal // analyses errors async _simulate() { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); let provider = this.getEthersProvider(); await provider.send("hardhat_impersonateAccount", [this.governor.address]); await provider.send("hardhat_setBalance", [this.governor.address, "0xffffffffffffffff"]); let governorSigner = await this.hre.ethers.getSigner(this.governor.address); let timelock = Timelock__factory_1.Timelock__factory.connect(await this.governor.timelock(), governorSigner); await provider.send("hardhat_impersonateAccount", [timelock.address]); await provider.send("hardhat_setBalance", [timelock.address, "0xffffffffffffffff"]); let timelockSigner = provider.getSigner(timelock.address); let blockInfo = await provider.getBlock("latest"); let delay = await timelock.delay(); let eta = delay.add(blockInfo.timestamp).add("50"); await provider.send("evm_setAutomine", [false]); for (let i = 0; i < this.targets.length; i++) { await timelock.queueTransaction(this.targets[i], this.values[i], this.signatures[i], this.calldatas[i], eta); } await this.mineBlocks(1); await this.mineBlock(eta.toNumber()); let receipts = new Array(); for (let i = 0; i < this.targets.length; i++) { await timelock.executeTransaction(this.targets[i], this.values[i], this.signatures[i], this.calldatas[i], eta).then(receipt => { receipts.push(receipt); }, async (timelockError) => { var _a; // analyse error let timelockErrorMessage = timelockError.error.message.match(/^[\w\s:]+'(.*)'$/m)[1]; let contractErrorMesage; // call the method on the contract as if it was the timelock // this will produce a more relavent message as to the failure of the action let contract = await ((_a = this.contracts[i]) === null || _a === void 0 ? void 0 : _a.connect(timelockSigner)); if (contract) { await contract.callStatic[this.signatures[i]](...this.args[i]).catch(contractError => { contractErrorMesage = contractError.message.match(/^[\w\s:]+'(.*)'$/m)[1]; }); } throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, `Proposal action ${i} failed. Target: ${this.targets[i]} Signature: ${this.signatures[i]} Args: ${this.args[i]}\n Timelock revert message: ${timelockErrorMessage} Contract revert message: ${contractErrorMesage}`); }); } await this.mineBlock(); for (let i = 0; i < this.targets.length; i++) { let r = await receipts[i].wait().catch(r => { return r.receipt; }); if (r.status != 1) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, `Action ${i} failed`); } } await provider.send("evm_setAutomine", [true]); await provider.send("hardhat_stopImpersonatingAccount", [this.governor.address]); await provider.send("hardhat_stopImpersonatingAccount", [timelock.address]); } async printProposalInfo() { if (!this.governor) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_GOVERNOR); if (!this.votingToken) throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.NO_VOTING_TOKEN); console.log('--------------------------------------------------------'); if (this.internalState == proposal_1.InternalProposalState.SUBMITTED) { const proposalInfo = await this.governor.proposals(this.id); const state = await this.getProposalState(); let votingTokenName = await this.votingToken.name(); let votingTokenDecimals = ethers_1.BigNumber.from("10").pow(await this.votingToken.decimals()); console.log(`Id: ${this.id.toString()}`); console.log(`Description: ${this.description}`); console.log(`For Votes: ${proposalInfo.forVotes.div(votingTokenDecimals)} ${votingTokenName} Votes`); console.log(`Agasint Votes: ${proposalInfo.againstVotes.div(votingTokenDecimals)} ${votingTokenName} Votes`); console.log(`Vote End: ${proposalInfo.endBlock}`); console.log(`State: ${state.toString()}`); } else { console.log("Unsubmitted proposal"); console.log(`Description: ${this.description}`); } for (let i = 0; i < this.targets.length; i++) { const contract = this.contracts[i]; const target = this.targets[i]; const signature = this.signatures[i]; const value = this.values[i]; const args = this.args[i]; let name = ""; if ((contract === null || contract === void 0 ? void 0 : contract.functions['name()']) != null) { name = (await contract.functions['name()']()).toString(); } console.log(`Action ${i}`); if (name == "") { console.log(` ├─ target ───── ${target}`); } else { console.log(` ├─ target ───── ${target} (name: ${name})`); } if (!value.isZero()) { console.log(` ├─ value ────── ${ethers_1.utils.formatEther(value.toString())} ETH`); } console.log(` ├─ signature ── ${signature}`); for (let j = 0; j < args.length - 1; j++) { const arg = args[j]; console.log(` ├─ args [ ${j} ] ─ ${arg}`); } console.log(` └─ args [ ${args.length - 1} ] ─ ${args[args.length - 1]}`); } } } exports.AlphaProposal = AlphaProposal; // -------------------- Define proposal builder -------------------- class AlphaProposalBuilder extends proposal_1.ProposalBuilder { constructor(hre, governor, votingToken, maxActions = 15) { super(hre); this.maxActions = maxActions; this.proposal = new AlphaProposal(hre, governor, votingToken); } setGovernor(governor) { this.proposal.setGovernor(governor); return this; } setVotingToken(votingToken) { this.proposal.setVotingToken(votingToken); return this; } addAction(target, value, signature, calldata) { if (this.proposal.targets.length >= this.maxActions) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.TOO_MANY_ACTIONS); } value = util_1.toBigNumber(value); const contract = null; const args = utils_2.defaultAbiCoder.decode([signature], calldata); this.proposal.addAction({ target, value, signature, calldata, contract, args }); return this; } addContractAction(contract, method, functionArgs, value) { if (this.proposal.targets.length >= this.maxActions) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, constants_1.errors.TOO_MANY_ACTIONS); } value = value ? util_1.toBigNumber(value) : util_1.toBigNumber("0"); const target = contract.address; const functionFragment = contract.interface.getFunction(method); const signature = functionFragment.format(utils_1.FormatTypes.sighash); if (functionFragment.inputs.length != functionArgs.length) { throw new plugins_1.HardhatPluginError(constants_1.PACKAGE_NAME, "arguments length do not match signature"); } // encode function call data const functionData = contract.interface.encodeFunctionData(functionFragment, functionArgs); const args = contract.interface.decodeFunctionData(functionFragment, functionData); const calldata = utils_1.hexDataSlice(functionData, 4); // Remove the sighash from the function data this.proposal.addAction({ target, value, signature, calldata, contract, args }); return this; } setProposer(proposer) { this.proposal.setProposer(proposer); return this; } /** * Set the description for the proposal * * Some UI interfaces for proposals require a newline `\n` * be added in the description to partition the proposal * title and the proposal description. It is at the users * descresion whether to add this or not. * * @param description The description field to set for the proposal */ setDescription(description) { this.proposal.description = description; return this; } build() { return this.proposal; } } exports.AlphaProposalBuilder = AlphaProposalBuilder; //# sourceMappingURL=compound-alpha.js.map