governance.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. waitForSnapshot(offset = 0) {
  103. const proposal = this.currentProposal;
  104. return this.governor.proposalSnapshot(proposal.id).then(timepoint => forward[this.mode](timepoint.addn(offset)));
  105. }
  106. waitForDeadline(offset = 0) {
  107. const proposal = this.currentProposal;
  108. return this.governor.proposalDeadline(proposal.id).then(timepoint => forward[this.mode](timepoint.addn(offset)));
  109. }
  110. waitForEta(offset = 0) {
  111. const proposal = this.currentProposal;
  112. return this.governor.proposalEta(proposal.id).then(timestamp => forward.timestamp(timestamp.addn(offset)));
  113. }
  114. /**
  115. * Specify a proposal either as
  116. * 1) an array of objects [{ target, value, data, signature? }]
  117. * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] }
  118. */
  119. setProposal(actions, description) {
  120. let targets, values, signatures, data, useCompatibilityInterface;
  121. if (Array.isArray(actions)) {
  122. useCompatibilityInterface = actions.some(a => 'signature' in a);
  123. targets = actions.map(a => a.target);
  124. values = actions.map(a => a.value || '0');
  125. signatures = actions.map(a => a.signature || '');
  126. data = actions.map(a => a.data || '0x');
  127. } else {
  128. useCompatibilityInterface = Array.isArray(actions.signatures);
  129. ({ targets, values, signatures = [], data } = actions);
  130. }
  131. const fulldata = zip(
  132. signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)),
  133. data,
  134. ).map(hexs => concatHex(...hexs));
  135. const descriptionHash = web3.utils.keccak256(description);
  136. // condensed version for queueing end executing
  137. const shortProposal = [targets, values, fulldata, descriptionHash];
  138. // full version for proposing
  139. const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description];
  140. // proposal id
  141. const id = web3.utils.toBN(
  142. web3.utils.keccak256(
  143. web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal),
  144. ),
  145. );
  146. this.currentProposal = {
  147. id,
  148. targets,
  149. values,
  150. signatures,
  151. data,
  152. fulldata,
  153. description,
  154. descriptionHash,
  155. shortProposal,
  156. fullProposal,
  157. useCompatibilityInterface,
  158. };
  159. return this.currentProposal;
  160. }
  161. }
  162. module.exports = {
  163. GovernorHelper,
  164. };