const { time } = require('@openzeppelin/test-helpers'); function zip(...args) { return Array(Math.max(...args.map(array => array.length))) .fill() .map((_, i) => args.map(array => array[i])); } function concatHex(...args) { return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x')))); } function concatOpts(args, opts = null) { return opts ? args.concat(opts) : args; } class GovernorHelper { constructor(governor) { this.governor = governor; } delegate(delegation = {}, opts = null) { return Promise.all([ delegation.token.delegate(delegation.to, { from: delegation.to }), delegation.value && delegation.token.transfer(...concatOpts([delegation.to, delegation.value]), opts), delegation.tokenId && delegation.token .ownerOf(delegation.tokenId) .then(owner => delegation.token.transferFrom(...concatOpts([owner, delegation.to, delegation.tokenId], opts)), ), ]); } propose(opts = null) { const proposal = this.currentProposal; return this.governor.methods[ proposal.useCompatibilityInterface ? 'propose(address[],uint256[],string[],bytes[],string)' : 'propose(address[],uint256[],bytes[],string)' ](...concatOpts(proposal.fullProposal, opts)); } queue(opts = null) { const proposal = this.currentProposal; return proposal.useCompatibilityInterface ? this.governor.methods['queue(uint256)'](...concatOpts([proposal.id], opts)) : this.governor.methods['queue(address[],uint256[],bytes[],bytes32)']( ...concatOpts(proposal.shortProposal, opts), ); } execute(opts = null) { const proposal = this.currentProposal; return proposal.useCompatibilityInterface ? this.governor.methods['execute(uint256)'](...concatOpts([proposal.id], opts)) : this.governor.methods['execute(address[],uint256[],bytes[],bytes32)']( ...concatOpts(proposal.shortProposal, opts), ); } cancel(opts = null) { const proposal = this.currentProposal; return proposal.useCompatibilityInterface ? this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts)) : this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)']( ...concatOpts(proposal.shortProposal, opts), ); } vote(vote = {}, opts = null) { const proposal = this.currentProposal; return vote.signature ? // if signature, and either params or reason → vote.params || vote.reason ? vote .signature({ proposalId: proposal.id, support: vote.support, reason: vote.reason || '', params: vote.params || '', }) .then(({ v, r, s }) => this.governor.castVoteWithReasonAndParamsBySig( ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts), ), ) : vote .signature({ proposalId: proposal.id, support: vote.support, }) .then(({ v, r, s }) => this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)), ) : vote.params ? // otherwise if params this.governor.castVoteWithReasonAndParams( ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts), ) : vote.reason ? // otherwise if reason this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts)) : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts)); } waitForSnapshot(offset = 0) { const proposal = this.currentProposal; return this.governor .proposalSnapshot(proposal.id) .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset))); } waitForDeadline(offset = 0) { const proposal = this.currentProposal; return this.governor .proposalDeadline(proposal.id) .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset))); } waitForEta(offset = 0) { const proposal = this.currentProposal; return this.governor.proposalEta(proposal.id).then(timestamp => time.increaseTo(timestamp.addn(offset))); } /** * Specify a proposal either as * 1) an array of objects [{ target, value, data, signature? }] * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] } */ setProposal(actions, description) { let targets, values, signatures, data, useCompatibilityInterface; if (Array.isArray(actions)) { useCompatibilityInterface = actions.some(a => 'signature' in a); targets = actions.map(a => a.target); values = actions.map(a => a.value || '0'); signatures = actions.map(a => a.signature || ''); data = actions.map(a => a.data || '0x'); } else { useCompatibilityInterface = Array.isArray(actions.signatures); ({ targets, values, signatures = [], data } = actions); } const fulldata = zip( signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)), data, ).map(hexs => concatHex(...hexs)); const descriptionHash = web3.utils.keccak256(description); // condensed version for queueing end executing const shortProposal = [targets, values, fulldata, descriptionHash]; // full version for proposing const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description]; // proposal id const id = web3.utils.toBN( web3.utils.keccak256( web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal), ), ); this.currentProposal = { id, targets, values, signatures, data, fulldata, description, descriptionHash, shortProposal, fullProposal, useCompatibilityInterface, }; return this.currentProposal; } } module.exports = { GovernorHelper, };