GovernorWorkflow.behavior.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. const { expectRevert, time } = require('@openzeppelin/test-helpers');
  2. async function getReceiptOrRevert (promise, error = undefined) {
  3. if (error) {
  4. await expectRevert(promise, error);
  5. return undefined;
  6. } else {
  7. const { receipt } = await promise;
  8. return receipt;
  9. }
  10. }
  11. function tryGet (obj, path = '') {
  12. try {
  13. return path.split('.').reduce((o, k) => o[k], obj);
  14. } catch (_) {
  15. return undefined;
  16. }
  17. }
  18. function zip (...args) {
  19. return Array(Math.max(...args.map(array => array.length)))
  20. .fill()
  21. .map((_, i) => args.map(array => array[i]));
  22. }
  23. function concatHex (...args) {
  24. return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x'))));
  25. }
  26. function runGovernorWorkflow () {
  27. beforeEach(async function () {
  28. this.receipts = {};
  29. // distinguish depending on the proposal length
  30. // - length 4: propose(address[], uint256[], bytes[], string) → GovernorCore
  31. // - length 5: propose(address[], uint256[], string[], bytes[], string) → GovernorCompatibilityBravo
  32. this.useCompatibilityInterface = this.settings.proposal.length === 5;
  33. // compute description hash
  34. this.descriptionHash = web3.utils.keccak256(this.settings.proposal.slice(-1).find(Boolean));
  35. // condensed proposal, used for queue and execute operation
  36. this.settings.shortProposal = [
  37. // targets
  38. this.settings.proposal[0],
  39. // values
  40. this.settings.proposal[1],
  41. // calldata (prefix selector if necessary)
  42. this.useCompatibilityInterface
  43. ? zip(
  44. this.settings.proposal[2].map(selector => selector && web3.eth.abi.encodeFunctionSignature(selector)),
  45. this.settings.proposal[3],
  46. ).map(hexs => concatHex(...hexs))
  47. : this.settings.proposal[2],
  48. // descriptionHash
  49. this.descriptionHash,
  50. ];
  51. // proposal id
  52. this.id = await this.mock.hashProposal(...this.settings.shortProposal);
  53. });
  54. it('run', async function () {
  55. // transfer tokens
  56. if (tryGet(this.settings, 'voters')) {
  57. for (const voter of this.settings.voters) {
  58. if (voter.weight) {
  59. await this.token.transfer(voter.voter, voter.weight, { from: this.settings.tokenHolder });
  60. }
  61. }
  62. }
  63. // propose
  64. if (this.mock.propose && tryGet(this.settings, 'steps.propose.enable') !== false) {
  65. this.receipts.propose = await getReceiptOrRevert(
  66. this.mock.methods[
  67. this.useCompatibilityInterface
  68. ? 'propose(address[],uint256[],string[],bytes[],string)'
  69. : 'propose(address[],uint256[],bytes[],string)'
  70. ](
  71. ...this.settings.proposal,
  72. { from: this.settings.proposer },
  73. ),
  74. tryGet(this.settings, 'steps.propose.error'),
  75. );
  76. if (tryGet(this.settings, 'steps.propose.error') === undefined) {
  77. this.deadline = await this.mock.proposalDeadline(this.id);
  78. this.snapshot = await this.mock.proposalSnapshot(this.id);
  79. }
  80. if (tryGet(this.settings, 'steps.propose.delay')) {
  81. await time.increase(tryGet(this.settings, 'steps.propose.delay'));
  82. }
  83. if (
  84. tryGet(this.settings, 'steps.propose.error') === undefined &&
  85. tryGet(this.settings, 'steps.propose.noadvance') !== true
  86. ) {
  87. await time.advanceBlockTo(this.snapshot.addn(1));
  88. }
  89. }
  90. // vote
  91. if (tryGet(this.settings, 'voters')) {
  92. this.receipts.castVote = [];
  93. for (const voter of this.settings.voters) {
  94. if (!voter.signature) {
  95. this.receipts.castVote.push(
  96. await getReceiptOrRevert(
  97. voter.reason
  98. ? this.mock.castVoteWithReason(this.id, voter.support, voter.reason, { from: voter.voter })
  99. : this.mock.castVote(this.id, voter.support, { from: voter.voter }),
  100. voter.error,
  101. ),
  102. );
  103. } else {
  104. const { v, r, s } = await voter.signature({ proposalId: this.id, support: voter.support });
  105. this.receipts.castVote.push(
  106. await getReceiptOrRevert(
  107. this.mock.castVoteBySig(this.id, voter.support, v, r, s),
  108. voter.error,
  109. ),
  110. );
  111. }
  112. if (tryGet(voter, 'delay')) {
  113. await time.increase(tryGet(voter, 'delay'));
  114. }
  115. }
  116. }
  117. // fast forward
  118. if (tryGet(this.settings, 'steps.wait.enable') !== false) {
  119. await time.advanceBlockTo(this.deadline.addn(1));
  120. }
  121. // queue
  122. if (this.mock.queue && tryGet(this.settings, 'steps.queue.enable') !== false) {
  123. this.receipts.queue = await getReceiptOrRevert(
  124. this.useCompatibilityInterface
  125. ? this.mock.methods['queue(uint256)'](
  126. this.id,
  127. { from: this.settings.queuer },
  128. )
  129. : this.mock.methods['queue(address[],uint256[],bytes[],bytes32)'](
  130. ...this.settings.shortProposal,
  131. { from: this.settings.queuer },
  132. ),
  133. tryGet(this.settings, 'steps.queue.error'),
  134. );
  135. this.eta = await this.mock.proposalEta(this.id);
  136. if (tryGet(this.settings, 'steps.queue.delay')) {
  137. await time.increase(tryGet(this.settings, 'steps.queue.delay'));
  138. }
  139. }
  140. // execute
  141. if (this.mock.execute && tryGet(this.settings, 'steps.execute.enable') !== false) {
  142. this.receipts.execute = await getReceiptOrRevert(
  143. this.useCompatibilityInterface
  144. ? this.mock.methods['execute(uint256)'](
  145. this.id,
  146. { from: this.settings.executer },
  147. )
  148. : this.mock.methods['execute(address[],uint256[],bytes[],bytes32)'](
  149. ...this.settings.shortProposal,
  150. { from: this.settings.executer },
  151. ),
  152. tryGet(this.settings, 'steps.execute.error'),
  153. );
  154. if (tryGet(this.settings, 'steps.execute.delay')) {
  155. await time.increase(tryGet(this.settings, 'steps.execute.delay'));
  156. }
  157. }
  158. });
  159. }
  160. module.exports = {
  161. runGovernorWorkflow,
  162. };