governance.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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(opts = null) {
  54. const proposal = this.currentProposal;
  55. return proposal.useCompatibilityInterface
  56. ? this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts))
  57. : this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)'](
  58. ...concatOpts(proposal.shortProposal, opts),
  59. );
  60. }
  61. vote(vote = {}, opts = null) {
  62. const proposal = this.currentProposal;
  63. return vote.signature
  64. ? // if signature, and either params or reason →
  65. vote.params || vote.reason
  66. ? vote
  67. .signature({
  68. proposalId: proposal.id,
  69. support: vote.support,
  70. reason: vote.reason || '',
  71. params: vote.params || '',
  72. })
  73. .then(({ v, r, s }) =>
  74. this.governor.castVoteWithReasonAndParamsBySig(
  75. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts),
  76. ),
  77. )
  78. : vote
  79. .signature({
  80. proposalId: proposal.id,
  81. support: vote.support,
  82. })
  83. .then(({ v, r, s }) =>
  84. this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)),
  85. )
  86. : vote.params
  87. ? // otherwise if params
  88. this.governor.castVoteWithReasonAndParams(
  89. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts),
  90. )
  91. : vote.reason
  92. ? // otherwise if reason
  93. this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts))
  94. : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts));
  95. }
  96. waitForSnapshot(offset = 0) {
  97. const proposal = this.currentProposal;
  98. return this.governor
  99. .proposalSnapshot(proposal.id)
  100. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  101. }
  102. waitForDeadline(offset = 0) {
  103. const proposal = this.currentProposal;
  104. return this.governor
  105. .proposalDeadline(proposal.id)
  106. .then(blockNumber => time.advanceBlockTo(blockNumber.addn(offset)));
  107. }
  108. waitForEta(offset = 0) {
  109. const proposal = this.currentProposal;
  110. return this.governor.proposalEta(proposal.id).then(timestamp => time.increaseTo(timestamp.addn(offset)));
  111. }
  112. /**
  113. * Specify a proposal either as
  114. * 1) an array of objects [{ target, value, data, signature? }]
  115. * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
  116. */
  117. setProposal(actions, description) {
  118. let targets, values, signatures, data, useCompatibilityInterface;
  119. if (Array.isArray(actions)) {
  120. useCompatibilityInterface = actions.some(a => 'signature' in a);
  121. targets = actions.map(a => a.target);
  122. values = actions.map(a => a.value || '0');
  123. signatures = actions.map(a => a.signature || '');
  124. data = actions.map(a => a.data || '0x');
  125. } else {
  126. useCompatibilityInterface = Array.isArray(actions.signatures);
  127. ({ targets, values, signatures = [], data } = actions);
  128. }
  129. const fulldata = zip(
  130. signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)),
  131. data,
  132. ).map(hexs => concatHex(...hexs));
  133. const descriptionHash = web3.utils.keccak256(description);
  134. // condensed version for queueing end executing
  135. const shortProposal = [targets, values, fulldata, descriptionHash];
  136. // full version for proposing
  137. const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description];
  138. // proposal id
  139. const id = web3.utils.toBN(
  140. web3.utils.keccak256(
  141. web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal),
  142. ),
  143. );
  144. this.currentProposal = {
  145. id,
  146. targets,
  147. values,
  148. signatures,
  149. data,
  150. fulldata,
  151. description,
  152. descriptionHash,
  153. shortProposal,
  154. fullProposal,
  155. useCompatibilityInterface,
  156. };
  157. return this.currentProposal;
  158. }
  159. }
  160. module.exports = {
  161. GovernorHelper,
  162. };