governance.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. const { time } = require('@openzeppelin/test-helpers');
  2. function zip(...args) {
  3. return Array(Math.max(...args.map(array => array.length)))
  4. .fill()
  5. .map((_, i) => args.map(array => array[i]));
  6. }
  7. function concatHex(...args) {
  8. return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x'))));
  9. }
  10. function concatOpts(args, opts = null) {
  11. return opts ? args.concat(opts) : args;
  12. }
  13. class GovernorHelper {
  14. constructor(governor) {
  15. this.governor = governor;
  16. }
  17. delegate(delegation = {}, opts = null) {
  18. return Promise.all([
  19. delegation.token.delegate(delegation.to, { from: delegation.to }),
  20. delegation.value && delegation.token.transfer(...concatOpts([delegation.to, delegation.value]), opts),
  21. delegation.tokenId &&
  22. delegation.token
  23. .ownerOf(delegation.tokenId)
  24. .then(owner =>
  25. delegation.token.transferFrom(...concatOpts([owner, delegation.to, delegation.tokenId], opts)),
  26. ),
  27. ]);
  28. }
  29. propose(opts = null) {
  30. const proposal = this.currentProposal;
  31. return this.governor.methods[
  32. proposal.useCompatibilityInterface
  33. ? 'propose(address[],uint256[],string[],bytes[],string)'
  34. : 'propose(address[],uint256[],bytes[],string)'
  35. ](...concatOpts(proposal.fullProposal, opts));
  36. }
  37. queue(opts = null) {
  38. const proposal = this.currentProposal;
  39. return proposal.useCompatibilityInterface
  40. ? this.governor.methods['queue(uint256)'](...concatOpts([proposal.id], opts))
  41. : this.governor.methods['queue(address[],uint256[],bytes[],bytes32)'](
  42. ...concatOpts(proposal.shortProposal, opts),
  43. );
  44. }
  45. execute(opts = null) {
  46. const proposal = this.currentProposal;
  47. return proposal.useCompatibilityInterface
  48. ? this.governor.methods['execute(uint256)'](...concatOpts([proposal.id], opts))
  49. : this.governor.methods['execute(address[],uint256[],bytes[],bytes32)'](
  50. ...concatOpts(proposal.shortProposal, opts),
  51. );
  52. }
  53. cancel(visibility = 'external', opts = null) {
  54. const proposal = this.currentProposal;
  55. switch (visibility) {
  56. case 'external':
  57. return this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts));
  58. case 'internal':
  59. return this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)'](
  60. ...concatOpts(proposal.shortProposal, opts),
  61. );
  62. default:
  63. throw new Error(`unsuported visibility "${visibility}"`);
  64. }
  65. }
  66. vote(vote = {}, opts = null) {
  67. const proposal = this.currentProposal;
  68. return vote.signature
  69. ? // if signature, and either params or reason →
  70. vote.params || vote.reason
  71. ? vote
  72. .signature(this.governor, {
  73. proposalId: proposal.id,
  74. support: vote.support,
  75. reason: vote.reason || '',
  76. params: vote.params || '',
  77. })
  78. .then(({ v, r, s }) =>
  79. this.governor.castVoteWithReasonAndParamsBySig(
  80. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts),
  81. ),
  82. )
  83. : vote
  84. .signature(this.governor, {
  85. proposalId: proposal.id,
  86. support: vote.support,
  87. })
  88. .then(({ v, r, s }) =>
  89. this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)),
  90. )
  91. : vote.params
  92. ? // otherwise if params
  93. this.governor.castVoteWithReasonAndParams(
  94. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts),
  95. )
  96. : vote.reason
  97. ? // otherwise if reason
  98. this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts))
  99. : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts));
  100. }
  101. waitForSnapshot(offset = 0) {
  102. const proposal = this.currentProposal;
  103. return this.governor
  104. .proposalSnapshot(proposal.id)
  105. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  106. }
  107. waitForDeadline(offset = 0) {
  108. const proposal = this.currentProposal;
  109. return this.governor
  110. .proposalDeadline(proposal.id)
  111. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  112. }
  113. waitForEta(offset = 0) {
  114. const proposal = this.currentProposal;
  115. return this.governor.proposalEta(proposal.id).then(timestamp => time.increaseTo(timestamp.addn(offset)));
  116. }
  117. /**
  118. * Specify a proposal either as
  119. * 1) an array of objects [{ target, value, data, signature? }]
  120. * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
  121. */
  122. setProposal(actions, description) {
  123. let targets, values, signatures, data, useCompatibilityInterface;
  124. if (Array.isArray(actions)) {
  125. useCompatibilityInterface = actions.some(a => 'signature' in a);
  126. targets = actions.map(a => a.target);
  127. values = actions.map(a => a.value || '0');
  128. signatures = actions.map(a => a.signature || '');
  129. data = actions.map(a => a.data || '0x');
  130. } else {
  131. useCompatibilityInterface = Array.isArray(actions.signatures);
  132. ({ targets, values, signatures = [], data } = actions);
  133. }
  134. const fulldata = zip(
  135. signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)),
  136. data,
  137. ).map(hexs => concatHex(...hexs));
  138. const descriptionHash = web3.utils.keccak256(description);
  139. // condensed version for queueing end executing
  140. const shortProposal = [targets, values, fulldata, descriptionHash];
  141. // full version for proposing
  142. const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description];
  143. // proposal id
  144. const id = web3.utils.toBN(
  145. web3.utils.keccak256(
  146. web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal),
  147. ),
  148. );
  149. this.currentProposal = {
  150. id,
  151. targets,
  152. values,
  153. signatures,
  154. data,
  155. fulldata,
  156. description,
  157. descriptionHash,
  158. shortProposal,
  159. fullProposal,
  160. useCompatibilityInterface,
  161. };
  162. return this.currentProposal;
  163. }
  164. }
  165. module.exports = {
  166. GovernorHelper,
  167. };