governance.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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 &&
  21. delegation.token.transfer(...concatOpts([ delegation.to, delegation.value ]), opts),
  22. delegation.tokenId &&
  23. delegation.token.ownerOf(delegation.tokenId).then(owner =>
  24. delegation.token.transferFrom(...concatOpts([ owner, delegation.to, delegation.tokenId ], opts)),
  25. ),
  26. ]);
  27. }
  28. propose (opts = null) {
  29. const proposal = this.currentProposal;
  30. return this.governor.methods[
  31. proposal.useCompatibilityInterface
  32. ? 'propose(address[],uint256[],string[],bytes[],string)'
  33. : 'propose(address[],uint256[],bytes[],string)'
  34. ](...concatOpts(proposal.fullProposal, opts));
  35. }
  36. queue (opts = null) {
  37. const proposal = this.currentProposal;
  38. return proposal.useCompatibilityInterface
  39. ? this.governor.methods['queue(uint256)'](...concatOpts(
  40. [ proposal.id ],
  41. opts,
  42. ))
  43. : this.governor.methods['queue(address[],uint256[],bytes[],bytes32)'](...concatOpts(
  44. proposal.shortProposal,
  45. opts,
  46. ));
  47. }
  48. execute (opts = null) {
  49. const proposal = this.currentProposal;
  50. return proposal.useCompatibilityInterface
  51. ? this.governor.methods['execute(uint256)'](...concatOpts(
  52. [ proposal.id ],
  53. opts,
  54. ))
  55. : this.governor.methods['execute(address[],uint256[],bytes[],bytes32)'](...concatOpts(
  56. proposal.shortProposal,
  57. opts,
  58. ));
  59. }
  60. cancel (opts = null) {
  61. const proposal = this.currentProposal;
  62. return proposal.useCompatibilityInterface
  63. ? this.governor.methods['cancel(uint256)'](...concatOpts(
  64. [ proposal.id ],
  65. opts,
  66. ))
  67. : this.governor.methods['cancel(address[],uint256[],bytes[],bytes32)'](...concatOpts(
  68. proposal.shortProposal,
  69. opts,
  70. ));
  71. }
  72. vote (vote = {}, opts = null) {
  73. const proposal = this.currentProposal;
  74. return vote.signature
  75. // if signature, and either params or reason →
  76. ? vote.params || vote.reason
  77. ? vote.signature({
  78. proposalId: proposal.id,
  79. support: vote.support,
  80. reason: vote.reason || '',
  81. params: vote.params || '',
  82. }).then(({ v, r, s }) => this.governor.castVoteWithReasonAndParamsBySig(...concatOpts(
  83. [ proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s ],
  84. opts,
  85. )))
  86. : vote.signature({
  87. proposalId: proposal.id,
  88. support: vote.support,
  89. }).then(({ v, r, s }) => this.governor.castVoteBySig(...concatOpts(
  90. [ proposal.id, vote.support, v, r, s ],
  91. opts,
  92. )))
  93. : vote.params
  94. // otherwise if params
  95. ? this.governor.castVoteWithReasonAndParams(...concatOpts(
  96. [ proposal.id, vote.support, vote.reason || '', vote.params ],
  97. opts,
  98. ))
  99. : vote.reason
  100. // otherwise if reason
  101. ? this.governor.castVoteWithReason(...concatOpts(
  102. [ proposal.id, vote.support, vote.reason ],
  103. opts,
  104. ))
  105. : this.governor.castVote(...concatOpts(
  106. [ proposal.id, vote.support ],
  107. opts,
  108. ));
  109. }
  110. waitForSnapshot (offset = 0) {
  111. const proposal = this.currentProposal;
  112. return this.governor.proposalSnapshot(proposal.id)
  113. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  114. }
  115. waitForDeadline (offset = 0) {
  116. const proposal = this.currentProposal;
  117. return this.governor.proposalDeadline(proposal.id)
  118. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  119. }
  120. waitForEta (offset = 0) {
  121. const proposal = this.currentProposal;
  122. return this.governor.proposalEta(proposal.id)
  123. .then(timestamp => time.increaseTo(timestamp.addn(offset)));
  124. }
  125. /**
  126. * Specify a proposal either as
  127. * 1) an array of objects [{ target, value, data, signature? }]
  128. * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
  129. */
  130. setProposal (actions, description) {
  131. let targets, values, signatures, data, useCompatibilityInterface;
  132. if (Array.isArray(actions)) {
  133. useCompatibilityInterface = actions.some(a => 'signature' in a);
  134. targets = actions.map(a => a.target);
  135. values = actions.map(a => a.value || '0');
  136. signatures = actions.map(a => a.signature || '');
  137. data = actions.map(a => a.data || '0x');
  138. } else {
  139. useCompatibilityInterface = Array.isArray(actions.signatures);
  140. ({ targets, values, signatures = [], data } = actions);
  141. }
  142. const fulldata = zip(signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)), data)
  143. .map(hexs => concatHex(...hexs));
  144. const descriptionHash = web3.utils.keccak256(description);
  145. // condensed version for queueing end executing
  146. const shortProposal = [
  147. targets,
  148. values,
  149. fulldata,
  150. descriptionHash,
  151. ];
  152. // full version for proposing
  153. const fullProposal = [
  154. targets,
  155. values,
  156. ...(useCompatibilityInterface ? [ signatures ] : []),
  157. data,
  158. description,
  159. ];
  160. // proposal id
  161. const id = web3.utils.toBN(web3.utils.keccak256(web3.eth.abi.encodeParameters(
  162. [ 'address[]', 'uint256[]', 'bytes[]', 'bytes32' ],
  163. shortProposal,
  164. )));
  165. this.currentProposal = {
  166. id,
  167. targets,
  168. values,
  169. signatures,
  170. data,
  171. fulldata,
  172. description,
  173. descriptionHash,
  174. shortProposal,
  175. fullProposal,
  176. useCompatibilityInterface,
  177. };
  178. return this.currentProposal;
  179. }
  180. }
  181. module.exports = {
  182. GovernorHelper,
  183. };