governance.js 6.4 KB

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