GovernorTimelockAccess.test.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. const { expectEvent } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const Enums = require('../../helpers/enums');
  4. const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance');
  5. const { expectRevertCustomError } = require('../../helpers/customError');
  6. const { clockFromReceipt } = require('../../helpers/time');
  7. const { selector } = require('../../helpers/methods');
  8. const AccessManager = artifacts.require('$AccessManager');
  9. const Governor = artifacts.require('$GovernorTimelockAccessMock');
  10. const AccessManagedTarget = artifacts.require('$AccessManagedTarget');
  11. const TOKENS = [
  12. // { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
  13. { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' },
  14. ];
  15. const hashOperation = (caller, target, data) =>
  16. web3.utils.keccak256(web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [caller, target, data]));
  17. contract('GovernorTimelockAccess', function (accounts) {
  18. const [admin, voter1, voter2, voter3, voter4] = accounts;
  19. const name = 'OZ-Governor';
  20. const version = '1';
  21. const tokenName = 'MockToken';
  22. const tokenSymbol = 'MTKN';
  23. const tokenSupply = web3.utils.toWei('100');
  24. const votingDelay = web3.utils.toBN(4);
  25. const votingPeriod = web3.utils.toBN(16);
  26. const value = web3.utils.toWei('1');
  27. for (const { mode, Token } of TOKENS) {
  28. describe(`using ${Token._json.contractName}`, function () {
  29. beforeEach(async function () {
  30. this.token = await Token.new(tokenName, tokenSymbol, tokenName, version);
  31. this.manager = await AccessManager.new(admin);
  32. this.mock = await Governor.new(
  33. name,
  34. votingDelay,
  35. votingPeriod,
  36. 0, // proposal threshold
  37. this.manager.address,
  38. 0, // base delay
  39. this.token.address,
  40. 0, // quorum
  41. );
  42. this.receiver = await AccessManagedTarget.new(this.manager.address);
  43. this.helper = new GovernorHelper(this.mock, mode);
  44. await web3.eth.sendTransaction({ from: admin, to: this.mock.address, value });
  45. await this.token.$_mint(admin, tokenSupply);
  46. await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: admin });
  47. await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: admin });
  48. await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: admin });
  49. await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: admin });
  50. // default proposals
  51. this.restricted = {};
  52. this.restricted.selector = this.receiver.contract.methods.fnRestricted().encodeABI();
  53. this.restricted.operation = {
  54. target: this.receiver.address,
  55. value: '0',
  56. data: this.restricted.selector,
  57. };
  58. this.restricted.operationId = hashOperation(
  59. this.mock.address,
  60. this.restricted.operation.target,
  61. this.restricted.operation.data,
  62. );
  63. this.unrestricted = {};
  64. this.unrestricted.selector = this.receiver.contract.methods.fnUnrestricted().encodeABI();
  65. this.unrestricted.operation = {
  66. target: this.receiver.address,
  67. value: '0',
  68. data: this.unrestricted.selector,
  69. };
  70. this.unrestricted.operationId = hashOperation(
  71. this.mock.address,
  72. this.unrestricted.operation.target,
  73. this.unrestricted.operation.data,
  74. );
  75. this.fallback = {};
  76. this.fallback.operation = {
  77. target: this.receiver.address,
  78. value: '0',
  79. data: '0x1234',
  80. };
  81. this.fallback.operationId = hashOperation(
  82. this.mock.address,
  83. this.fallback.operation.target,
  84. this.fallback.operation.data,
  85. );
  86. });
  87. it('accepts ether transfers', async function () {
  88. await web3.eth.sendTransaction({ from: admin, to: this.mock.address, value: 1 });
  89. });
  90. it('post deployment check', async function () {
  91. expect(await this.mock.name()).to.be.equal(name);
  92. expect(await this.mock.token()).to.be.equal(this.token.address);
  93. expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay);
  94. expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod);
  95. expect(await this.mock.quorum(0)).to.be.bignumber.equal('0');
  96. expect(await this.mock.accessManager()).to.be.equal(this.manager.address);
  97. });
  98. describe('base delay only', function () {
  99. for (const [delay, queue] of [
  100. [0, true],
  101. [0, false],
  102. [1000, true],
  103. ]) {
  104. it(`delay ${delay}, ${queue ? 'with' : 'without'} queuing`, async function () {
  105. await this.mock.$_setBaseDelaySeconds(delay);
  106. this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr');
  107. await this.helper.propose();
  108. await this.helper.waitForSnapshot();
  109. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  110. await this.helper.waitForDeadline();
  111. if (queue) {
  112. const txQueue = await this.helper.queue();
  113. expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
  114. }
  115. if (delay > 0) {
  116. await this.helper.waitForEta();
  117. }
  118. const txExecute = await this.helper.execute();
  119. expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
  120. expectEvent.inTransaction(txExecute, this.receiver, 'CalledUnrestricted');
  121. });
  122. }
  123. });
  124. it('single operation with access manager delay', async function () {
  125. const delay = 1000;
  126. const roleId = '1';
  127. await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, {
  128. from: admin,
  129. });
  130. await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin });
  131. this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
  132. await this.helper.propose();
  133. await this.helper.waitForSnapshot();
  134. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  135. await this.helper.waitForDeadline();
  136. const txQueue = await this.helper.queue();
  137. await this.helper.waitForEta();
  138. const txExecute = await this.helper.execute();
  139. expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
  140. await expectEvent.inTransaction(txQueue.tx, this.manager, 'OperationScheduled', {
  141. operationId: this.restricted.operationId,
  142. nonce: '1',
  143. schedule: web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(delay),
  144. caller: this.mock.address,
  145. target: this.restricted.operation.target,
  146. data: this.restricted.operation.data,
  147. });
  148. expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
  149. await expectEvent.inTransaction(txExecute.tx, this.manager, 'OperationExecuted', {
  150. operationId: this.restricted.operationId,
  151. nonce: '1',
  152. });
  153. await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledRestricted');
  154. });
  155. it('bundle of varied operations', async function () {
  156. const managerDelay = 1000;
  157. const roleId = '1';
  158. const baseDelay = managerDelay * 2;
  159. await this.mock.$_setBaseDelaySeconds(baseDelay);
  160. await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, {
  161. from: admin,
  162. });
  163. await this.manager.grantRole(roleId, this.mock.address, managerDelay, { from: admin });
  164. this.proposal = await this.helper.setProposal(
  165. [this.restricted.operation, this.unrestricted.operation, this.fallback.operation],
  166. 'descr',
  167. );
  168. await this.helper.propose();
  169. await this.helper.waitForSnapshot();
  170. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  171. await this.helper.waitForDeadline();
  172. const txQueue = await this.helper.queue();
  173. await this.helper.waitForEta();
  174. const txExecute = await this.helper.execute();
  175. expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id });
  176. await expectEvent.inTransaction(txQueue.tx, this.manager, 'OperationScheduled', {
  177. operationId: this.restricted.operationId,
  178. nonce: '1',
  179. schedule: web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(baseDelay),
  180. caller: this.mock.address,
  181. target: this.restricted.operation.target,
  182. data: this.restricted.operation.data,
  183. });
  184. expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id });
  185. await expectEvent.inTransaction(txExecute.tx, this.manager, 'OperationExecuted', {
  186. operationId: this.restricted.operationId,
  187. nonce: '1',
  188. });
  189. await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledRestricted');
  190. await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledUnrestricted');
  191. await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledFallback');
  192. });
  193. describe('cancel', function () {
  194. const delay = 1000;
  195. const roleId = '1';
  196. beforeEach(async function () {
  197. await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, {
  198. from: admin,
  199. });
  200. await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin });
  201. });
  202. it('cancellation after queue (internal)', async function () {
  203. this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
  204. await this.helper.propose();
  205. await this.helper.waitForSnapshot();
  206. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  207. await this.helper.waitForDeadline();
  208. await this.helper.queue();
  209. const txCancel = await this.helper.cancel('internal');
  210. expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id });
  211. await expectEvent.inTransaction(txCancel.tx, this.manager, 'OperationCanceled', {
  212. operationId: this.restricted.operationId,
  213. nonce: '1',
  214. });
  215. await this.helper.waitForEta();
  216. await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
  217. this.proposal.id,
  218. Enums.ProposalState.Canceled,
  219. proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
  220. ]);
  221. });
  222. it('cancel calls already canceled by guardian', async function () {
  223. const operationA = { target: this.receiver.address, data: this.restricted.selector + '00' };
  224. const operationB = { target: this.receiver.address, data: this.restricted.selector + '01' };
  225. const operationC = { target: this.receiver.address, data: this.restricted.selector + '02' };
  226. const operationAId = hashOperation(this.mock.address, operationA.target, operationA.data);
  227. const operationBId = hashOperation(this.mock.address, operationB.target, operationB.data);
  228. const proposal1 = new GovernorHelper(this.mock, mode);
  229. const proposal2 = new GovernorHelper(this.mock, mode);
  230. proposal1.setProposal([operationA, operationB], 'proposal A+B');
  231. proposal2.setProposal([operationA, operationC], 'proposal A+C');
  232. for (const p of [proposal1, proposal2]) {
  233. await p.propose();
  234. await p.waitForSnapshot();
  235. await p.vote({ support: Enums.VoteType.For }, { from: voter1 });
  236. await p.waitForDeadline();
  237. }
  238. // Can queue the first proposal
  239. await proposal1.queue();
  240. // Cannot queue the second proposal: operation A already scheduled with delay
  241. await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]);
  242. // Admin cancels operation B on the manager
  243. await this.manager.cancel(this.mock.address, operationB.target, operationB.data, { from: admin });
  244. // Still cannot queue the second proposal: operation A already scheduled with delay
  245. await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]);
  246. await proposal1.waitForEta();
  247. // Cannot execute first proposal: operation B has been canceled
  248. await expectRevertCustomError(proposal1.execute(), 'AccessManagerNotScheduled', [operationBId]);
  249. // Cancel the first proposal to release operation A
  250. await proposal1.cancel('internal');
  251. // can finally queue the second proposal
  252. await proposal2.queue();
  253. await proposal2.waitForEta();
  254. // Can execute second proposal
  255. await proposal2.execute();
  256. });
  257. });
  258. describe('ignore AccessManager', function () {
  259. it('defaults', async function () {
  260. expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal(
  261. false,
  262. );
  263. expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(true);
  264. });
  265. it('internal setter', async function () {
  266. const p1 = { target: this.receiver.address, selector: this.restricted.selector, ignored: true };
  267. const tx1 = await this.mock.$_setAccessManagerIgnored(p1.target, p1.selector, p1.ignored);
  268. expect(await this.mock.isAccessManagerIgnored(p1.target, p1.selector)).to.equal(p1.ignored);
  269. expectEvent(tx1, 'AccessManagerIgnoredSet', p1);
  270. const p2 = { target: this.mock.address, selector: '0x12341234', ignored: false };
  271. const tx2 = await this.mock.$_setAccessManagerIgnored(p2.target, p2.selector, p2.ignored);
  272. expect(await this.mock.isAccessManagerIgnored(p2.target, p2.selector)).to.equal(p2.ignored);
  273. expectEvent(tx2, 'AccessManagerIgnoredSet', p2);
  274. });
  275. it('external setter', async function () {
  276. const setAccessManagerIgnored = (...args) =>
  277. this.mock.contract.methods.setAccessManagerIgnored(...args).encodeABI();
  278. await this.helper.setProposal(
  279. [
  280. {
  281. target: this.mock.address,
  282. data: setAccessManagerIgnored(
  283. this.receiver.address,
  284. [this.restricted.selector, this.unrestricted.selector],
  285. true,
  286. ),
  287. value: '0',
  288. },
  289. {
  290. target: this.mock.address,
  291. data: setAccessManagerIgnored(this.mock.address, ['0x12341234', '0x67896789'], false),
  292. value: '0',
  293. },
  294. ],
  295. 'descr',
  296. );
  297. await this.helper.propose();
  298. await this.helper.waitForSnapshot();
  299. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  300. await this.helper.waitForDeadline();
  301. const tx = await this.helper.execute();
  302. expectEvent(tx, 'AccessManagerIgnoredSet');
  303. expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal(
  304. true,
  305. );
  306. expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.unrestricted.selector)).to.equal(
  307. true,
  308. );
  309. expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(false);
  310. expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x67896789')).to.equal(false);
  311. });
  312. it('locked function', async function () {
  313. const setAccessManagerIgnored = selector('setAccessManagerIgnored(address,bytes4[],bool)');
  314. await expectRevertCustomError(
  315. this.mock.$_setAccessManagerIgnored(this.mock.address, setAccessManagerIgnored, true),
  316. 'GovernorLockedIgnore',
  317. [],
  318. );
  319. await this.mock.$_setAccessManagerIgnored(this.receiver.address, setAccessManagerIgnored, true);
  320. });
  321. it('ignores access manager', async function () {
  322. const amount = 100;
  323. const target = this.token.address;
  324. const data = this.token.contract.methods.transfer(voter4, amount).encodeABI();
  325. const selector = data.slice(0, 10);
  326. await this.token.$_mint(this.mock.address, amount);
  327. const roleId = '1';
  328. await this.manager.setTargetFunctionRole(target, [selector], roleId, { from: admin });
  329. await this.manager.grantRole(roleId, this.mock.address, 0, { from: admin });
  330. await this.helper.setProposal([{ target, data, value: '0' }], '1');
  331. await this.helper.propose();
  332. await this.helper.waitForSnapshot();
  333. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  334. await this.helper.waitForDeadline();
  335. await expectRevertCustomError(this.helper.execute(), 'ERC20InsufficientBalance', [
  336. this.manager.address,
  337. 0,
  338. amount,
  339. ]);
  340. await this.mock.$_setAccessManagerIgnored(target, selector, true);
  341. await this.helper.setProposal([{ target, data, value: '0' }], '2');
  342. await this.helper.propose();
  343. await this.helper.waitForSnapshot();
  344. await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
  345. await this.helper.waitForDeadline();
  346. const tx = await this.helper.execute();
  347. expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.mock.address });
  348. });
  349. });
  350. });
  351. }
  352. });