governance.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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. return this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts));
  59. case 'internal':
  60. return this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)'](
  61. ...concatOpts(proposal.shortProposal, opts),
  62. );
  63. default:
  64. throw new Error(`unsuported visibility "${visibility}"`);
  65. }
  66. }
  67. vote(vote = {}, opts = null) {
  68. const proposal = this.currentProposal;
  69. return vote.signature
  70. ? // if signature, and either params or reason →
  71. vote.params || vote.reason
  72. ? vote
  73. .signature(this.governor, {
  74. proposalId: proposal.id,
  75. support: vote.support,
  76. reason: vote.reason || '',
  77. params: vote.params || '',
  78. })
  79. .then(({ v, r, s }) =>
  80. this.governor.castVoteWithReasonAndParamsBySig(
  81. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts),
  82. ),
  83. )
  84. : vote
  85. .signature(this.governor, {
  86. proposalId: proposal.id,
  87. support: vote.support,
  88. })
  89. .then(({ v, r, s }) =>
  90. this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)),
  91. )
  92. : vote.params
  93. ? // otherwise if params
  94. this.governor.castVoteWithReasonAndParams(
  95. ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts),
  96. )
  97. : vote.reason
  98. ? // otherwise if reason
  99. this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts))
  100. : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts));
  101. }
  102. async waitForSnapshot(offset = 0) {
  103. const proposal = this.currentProposal;
  104. const timepoint = await this.governor.proposalSnapshot(proposal.id);
  105. return forward[this.mode](timepoint.addn(offset));
  106. }
  107. async waitForDeadline(offset = 0) {
  108. const proposal = this.currentProposal;
  109. const timepoint = await this.governor.proposalDeadline(proposal.id);
  110. return forward[this.mode](timepoint.addn(offset));
  111. }
  112. async waitForEta(offset = 0) {
  113. const proposal = this.currentProposal;
  114. const timestamp = await this.governor.proposalEta(proposal.id);
  115. return forward.timestamp(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. };