GovernorTimelockAccess.test.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs');
  5. const { GovernorHelper } = require('../../helpers/governance');
  6. const { hashOperation } = require('../../helpers/access-manager');
  7. const { max } = require('../../helpers/math');
  8. const { selector } = require('../../helpers/methods');
  9. const { ProposalState, VoteType } = require('../../helpers/enums');
  10. const time = require('../../helpers/time');
  11. function prepareOperation({ sender, target, value = 0n, data = '0x' }) {
  12. return {
  13. id: hashOperation(sender, target, data),
  14. operation: { target, value, data },
  15. selector: data.slice(0, 10).padEnd(10, '0'),
  16. };
  17. }
  18. const TOKENS = [
  19. { Token: '$ERC20Votes', mode: 'blocknumber' },
  20. { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' },
  21. ];
  22. const name = 'OZ-Governor';
  23. const version = '1';
  24. const tokenName = 'MockToken';
  25. const tokenSymbol = 'MTKN';
  26. const tokenSupply = ethers.parseEther('100');
  27. const votingDelay = 4n;
  28. const votingPeriod = 16n;
  29. const value = ethers.parseEther('1');
  30. describe('GovernorTimelockAccess', function () {
  31. for (const { Token, mode } of TOKENS) {
  32. const fixture = async () => {
  33. const [admin, voter1, voter2, voter3, voter4, other] = await ethers.getSigners();
  34. const manager = await ethers.deployContract('$AccessManager', [admin]);
  35. const receiver = await ethers.deployContract('$AccessManagedTarget', [manager]);
  36. const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]);
  37. const mock = await ethers.deployContract('$GovernorTimelockAccessMock', [
  38. name,
  39. votingDelay,
  40. votingPeriod,
  41. 0n,
  42. manager,
  43. 0n,
  44. token,
  45. 0n,
  46. ]);
  47. await admin.sendTransaction({ to: mock, value });
  48. await token.$_mint(admin, tokenSupply);
  49. const helper = new GovernorHelper(mock, mode);
  50. await helper.connect(admin).delegate({ token, to: voter1, value: ethers.parseEther('10') });
  51. await helper.connect(admin).delegate({ token, to: voter2, value: ethers.parseEther('7') });
  52. await helper.connect(admin).delegate({ token, to: voter3, value: ethers.parseEther('5') });
  53. await helper.connect(admin).delegate({ token, to: voter4, value: ethers.parseEther('2') });
  54. return { admin, voter1, voter2, voter3, voter4, other, manager, receiver, token, mock, helper };
  55. };
  56. describe(`using ${Token}`, function () {
  57. beforeEach(async function () {
  58. Object.assign(this, await loadFixture(fixture));
  59. // restricted proposal
  60. this.restricted = prepareOperation({
  61. sender: this.mock.target,
  62. target: this.receiver.target,
  63. data: this.receiver.interface.encodeFunctionData('fnRestricted'),
  64. });
  65. this.unrestricted = prepareOperation({
  66. sender: this.mock.target,
  67. target: this.receiver.target,
  68. data: this.receiver.interface.encodeFunctionData('fnUnrestricted'),
  69. });
  70. this.fallback = prepareOperation({
  71. sender: this.mock.target,
  72. target: this.receiver.target,
  73. data: '0x1234',
  74. });
  75. });
  76. it('accepts ether transfers', async function () {
  77. await this.admin.sendTransaction({ to: this.mock, value: 1n });
  78. });
  79. it('post deployment check', async function () {
  80. expect(await this.mock.name()).to.equal(name);
  81. expect(await this.mock.token()).to.equal(this.token);
  82. expect(await this.mock.votingDelay()).to.equal(votingDelay);
  83. expect(await this.mock.votingPeriod()).to.equal(votingPeriod);
  84. expect(await this.mock.quorum(0n)).to.equal(0n);
  85. expect(await this.mock.accessManager()).to.equal(this.manager);
  86. });
  87. it('sets base delay (seconds)', async function () {
  88. const baseDelay = time.duration.hours(10n);
  89. // Only through governance
  90. await expect(this.mock.connect(this.voter1).setBaseDelaySeconds(baseDelay))
  91. .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
  92. .withArgs(this.voter1);
  93. this.proposal = await this.helper.setProposal(
  94. [
  95. {
  96. target: this.mock.target,
  97. data: this.mock.interface.encodeFunctionData('setBaseDelaySeconds', [baseDelay]),
  98. },
  99. ],
  100. 'descr',
  101. );
  102. await this.helper.propose();
  103. await this.helper.waitForSnapshot();
  104. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  105. await this.helper.waitForDeadline();
  106. await expect(this.helper.execute()).to.emit(this.mock, 'BaseDelaySet').withArgs(0n, baseDelay);
  107. expect(await this.mock.baseDelaySeconds()).to.equal(baseDelay);
  108. });
  109. it('sets access manager ignored', async function () {
  110. const selectors = ['0x12345678', '0x87654321', '0xabcdef01'];
  111. // Only through governance
  112. await expect(this.mock.connect(this.voter1).setAccessManagerIgnored(this.other, selectors, true))
  113. .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor')
  114. .withArgs(this.voter1);
  115. // Ignore
  116. await this.helper.setProposal(
  117. [
  118. {
  119. target: this.mock.target,
  120. data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [
  121. this.other.address,
  122. selectors,
  123. true,
  124. ]),
  125. },
  126. ],
  127. 'descr',
  128. );
  129. await this.helper.propose();
  130. await this.helper.waitForSnapshot();
  131. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  132. await this.helper.waitForDeadline();
  133. const ignoreReceipt = this.helper.execute();
  134. for (const selector of selectors) {
  135. await expect(ignoreReceipt)
  136. .to.emit(this.mock, 'AccessManagerIgnoredSet')
  137. .withArgs(this.other, selector, true);
  138. expect(await this.mock.isAccessManagerIgnored(this.other, selector)).to.be.true;
  139. }
  140. // Unignore
  141. await this.helper.setProposal(
  142. [
  143. {
  144. target: this.mock.target,
  145. data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [
  146. this.other.address,
  147. selectors,
  148. false,
  149. ]),
  150. },
  151. ],
  152. 'descr',
  153. );
  154. await this.helper.propose();
  155. await this.helper.waitForSnapshot();
  156. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  157. await this.helper.waitForDeadline();
  158. const unignoreReceipt = this.helper.execute();
  159. for (const selector of selectors) {
  160. await expect(unignoreReceipt)
  161. .to.emit(this.mock, 'AccessManagerIgnoredSet')
  162. .withArgs(this.other, selector, false);
  163. expect(await this.mock.isAccessManagerIgnored(this.other, selector)).to.be.false;
  164. }
  165. });
  166. it('sets access manager ignored when target is the governor', async function () {
  167. const selectors = ['0x12345678', '0x87654321', '0xabcdef01'];
  168. await this.helper.setProposal(
  169. [
  170. {
  171. target: this.mock.target,
  172. data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [
  173. this.mock.target,
  174. selectors,
  175. true,
  176. ]),
  177. },
  178. ],
  179. 'descr',
  180. );
  181. await this.helper.propose();
  182. await this.helper.waitForSnapshot();
  183. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  184. await this.helper.waitForDeadline();
  185. const tx = this.helper.execute();
  186. for (const selector of selectors) {
  187. await expect(tx).to.emit(this.mock, 'AccessManagerIgnoredSet').withArgs(this.mock, selector, true);
  188. expect(await this.mock.isAccessManagerIgnored(this.mock, selector)).to.be.true;
  189. }
  190. });
  191. it('does not need to queue proposals with no delay', async function () {
  192. const roleId = 1n;
  193. const executionDelay = 0n;
  194. const baseDelay = 0n;
  195. // Set execution delay
  196. await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  197. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  198. // Set base delay
  199. await this.mock.$_setBaseDelaySeconds(baseDelay);
  200. await this.helper.setProposal([this.restricted.operation], 'descr');
  201. await this.helper.propose();
  202. expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.false;
  203. });
  204. it('needs to queue proposals with any delay', async function () {
  205. const roleId = 1n;
  206. const delays = [
  207. [time.duration.hours(1n), time.duration.hours(2n)],
  208. [time.duration.hours(2n), time.duration.hours(1n)],
  209. ];
  210. for (const [executionDelay, baseDelay] of delays) {
  211. // Set execution delay
  212. await this.manager
  213. .connect(this.admin)
  214. .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  215. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  216. // Set base delay
  217. await this.mock.$_setBaseDelaySeconds(baseDelay);
  218. await this.helper.setProposal(
  219. [this.restricted.operation],
  220. `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`,
  221. );
  222. await this.helper.propose();
  223. expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.true;
  224. }
  225. });
  226. describe('execution plan', function () {
  227. it('returns plan for delayed operations', async function () {
  228. const roleId = 1n;
  229. const delays = [
  230. [time.duration.hours(1n), time.duration.hours(2n)],
  231. [time.duration.hours(2n), time.duration.hours(1n)],
  232. ];
  233. for (const [executionDelay, baseDelay] of delays) {
  234. // Set execution delay
  235. await this.manager
  236. .connect(this.admin)
  237. .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  238. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  239. // Set base delay
  240. await this.mock.$_setBaseDelaySeconds(baseDelay);
  241. this.proposal = await this.helper.setProposal(
  242. [this.restricted.operation],
  243. `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`,
  244. );
  245. await this.helper.propose();
  246. expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([
  247. max(baseDelay, executionDelay),
  248. [true],
  249. [true],
  250. ]);
  251. }
  252. });
  253. it('returns plan for not delayed operations', async function () {
  254. const roleId = 1n;
  255. const executionDelay = 0n;
  256. const baseDelay = 0n;
  257. // Set execution delay
  258. await this.manager
  259. .connect(this.admin)
  260. .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  261. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  262. // Set base delay
  263. await this.mock.$_setBaseDelaySeconds(baseDelay);
  264. this.proposal = await this.helper.setProposal([this.restricted.operation], `descr`);
  265. await this.helper.propose();
  266. expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([0n, [true], [false]]);
  267. });
  268. it('returns plan for an operation ignoring the manager', async function () {
  269. await this.mock.$_setAccessManagerIgnored(this.receiver, this.restricted.selector, true);
  270. const roleId = 1n;
  271. const delays = [
  272. [time.duration.hours(1n), time.duration.hours(2n)],
  273. [time.duration.hours(2n), time.duration.hours(1n)],
  274. ];
  275. for (const [executionDelay, baseDelay] of delays) {
  276. // Set execution delay
  277. await this.manager
  278. .connect(this.admin)
  279. .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  280. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  281. // Set base delay
  282. await this.mock.$_setBaseDelaySeconds(baseDelay);
  283. this.proposal = await this.helper.setProposal(
  284. [this.restricted.operation],
  285. `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`,
  286. );
  287. await this.helper.propose();
  288. expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([
  289. baseDelay,
  290. [false],
  291. [false],
  292. ]);
  293. }
  294. });
  295. });
  296. describe('base delay only', function () {
  297. for (const [delay, queue] of [
  298. [0, true],
  299. [0, false],
  300. [1000, true],
  301. ]) {
  302. it(`delay ${delay}, ${queue ? 'with' : 'without'} queuing`, async function () {
  303. await this.mock.$_setBaseDelaySeconds(delay);
  304. this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr');
  305. await this.helper.propose();
  306. await this.helper.waitForSnapshot();
  307. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  308. await this.helper.waitForDeadline();
  309. if (await this.mock.proposalNeedsQueuing(this.proposal.id)) {
  310. expect(await this.helper.queue())
  311. .to.emit(this.mock, 'ProposalQueued')
  312. .withArgs(this.proposal.id, anyValue);
  313. }
  314. if (delay > 0) {
  315. await this.helper.waitForEta();
  316. }
  317. expect(await this.helper.execute())
  318. .to.emit(this.mock, 'ProposalExecuted')
  319. .withArgs(this.proposal.id)
  320. .to.emit(this.receiver, 'CalledUnrestricted');
  321. });
  322. }
  323. });
  324. it('reverts when an operation is executed before eta', async function () {
  325. const delay = time.duration.hours(2n);
  326. await this.mock.$_setBaseDelaySeconds(delay);
  327. this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr');
  328. await this.helper.propose();
  329. await this.helper.waitForSnapshot();
  330. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  331. await this.helper.waitForDeadline();
  332. await this.helper.queue();
  333. await expect(this.helper.execute())
  334. .to.be.revertedWithCustomError(this.mock, 'GovernorUnmetDelay')
  335. .withArgs(this.proposal.id, await this.mock.proposalEta(this.proposal.id));
  336. });
  337. it('reverts with a proposal including multiple operations but one of those was cancelled in the manager', async function () {
  338. const delay = time.duration.hours(2n);
  339. const roleId = 1n;
  340. await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  341. await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay);
  342. // Set proposals
  343. const original = new GovernorHelper(this.mock, mode);
  344. await original.setProposal([this.restricted.operation, this.unrestricted.operation], 'descr');
  345. // Go through all the governance process
  346. await original.propose();
  347. await original.waitForSnapshot();
  348. await original.connect(this.voter1).vote({ support: VoteType.For });
  349. await original.waitForDeadline();
  350. await original.queue();
  351. await original.waitForEta();
  352. // Suddenly cancel one of the proposed operations in the manager
  353. await this.manager
  354. .connect(this.admin)
  355. .cancel(this.mock, this.restricted.operation.target, this.restricted.operation.data);
  356. // Reschedule the same operation in a different proposal to avoid "AccessManagerNotScheduled" error
  357. const rescheduled = new GovernorHelper(this.mock, mode);
  358. await rescheduled.setProposal([this.restricted.operation], 'descr');
  359. await rescheduled.propose();
  360. await rescheduled.waitForSnapshot();
  361. await rescheduled.connect(this.voter1).vote({ support: VoteType.For });
  362. await rescheduled.waitForDeadline();
  363. await rescheduled.queue(); // This will schedule it again in the manager
  364. await rescheduled.waitForEta();
  365. // Attempt to execute
  366. await expect(original.execute())
  367. .to.be.revertedWithCustomError(this.mock, 'GovernorMismatchedNonce')
  368. .withArgs(original.currentProposal.id, 1, 2);
  369. });
  370. it('single operation with access manager delay', async function () {
  371. const delay = 1000n;
  372. const roleId = 1n;
  373. await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  374. await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay);
  375. this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
  376. await this.helper.propose();
  377. await this.helper.waitForSnapshot();
  378. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  379. await this.helper.waitForDeadline();
  380. const txQueue = await this.helper.queue();
  381. await this.helper.waitForEta();
  382. const txExecute = await this.helper.execute();
  383. await expect(txQueue)
  384. .to.emit(this.mock, 'ProposalQueued')
  385. .withArgs(this.proposal.id, anyValue)
  386. .to.emit(this.manager, 'OperationScheduled')
  387. .withArgs(
  388. this.restricted.id,
  389. 1n,
  390. (await time.clockFromReceipt.timestamp(txQueue)) + delay,
  391. this.mock.target,
  392. this.restricted.operation.target,
  393. this.restricted.operation.data,
  394. );
  395. await expect(txExecute)
  396. .to.emit(this.mock, 'ProposalExecuted')
  397. .withArgs(this.proposal.id)
  398. .to.emit(this.manager, 'OperationExecuted')
  399. .withArgs(this.restricted.id, 1n)
  400. .to.emit(this.receiver, 'CalledRestricted');
  401. });
  402. it('bundle of varied operations', async function () {
  403. const managerDelay = 1000n;
  404. const roleId = 1n;
  405. const baseDelay = managerDelay * 2n;
  406. await this.mock.$_setBaseDelaySeconds(baseDelay);
  407. await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  408. await this.manager.connect(this.admin).grantRole(roleId, this.mock, managerDelay);
  409. this.proposal = await this.helper.setProposal(
  410. [this.restricted.operation, this.unrestricted.operation, this.fallback.operation],
  411. 'descr',
  412. );
  413. await this.helper.propose();
  414. await this.helper.waitForSnapshot();
  415. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  416. await this.helper.waitForDeadline();
  417. const txQueue = await this.helper.queue();
  418. await this.helper.waitForEta();
  419. const txExecute = await this.helper.execute();
  420. await expect(txQueue)
  421. .to.emit(this.mock, 'ProposalQueued')
  422. .withArgs(this.proposal.id, anyValue)
  423. .to.emit(this.manager, 'OperationScheduled')
  424. .withArgs(
  425. this.restricted.id,
  426. 1n,
  427. (await time.clockFromReceipt.timestamp(txQueue)) + baseDelay,
  428. this.mock.target,
  429. this.restricted.operation.target,
  430. this.restricted.operation.data,
  431. );
  432. await expect(txExecute)
  433. .to.emit(this.mock, 'ProposalExecuted')
  434. .withArgs(this.proposal.id)
  435. .to.emit(this.manager, 'OperationExecuted')
  436. .withArgs(this.restricted.id, 1n)
  437. .to.emit(this.receiver, 'CalledRestricted')
  438. .to.emit(this.receiver, 'CalledUnrestricted')
  439. .to.emit(this.receiver, 'CalledFallback');
  440. });
  441. describe('cancel', function () {
  442. const delay = 1000n;
  443. const roleId = 1n;
  444. beforeEach(async function () {
  445. await this.manager
  446. .connect(this.admin)
  447. .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId);
  448. await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay);
  449. });
  450. it('cancels restricted with delay after queue (internal)', async function () {
  451. this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
  452. await this.helper.propose();
  453. await this.helper.waitForSnapshot();
  454. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  455. await this.helper.waitForDeadline();
  456. await this.helper.queue();
  457. await expect(this.helper.cancel('internal'))
  458. .to.emit(this.mock, 'ProposalCanceled')
  459. .withArgs(this.proposal.id)
  460. .to.emit(this.manager, 'OperationCanceled')
  461. .withArgs(this.restricted.id, 1n);
  462. await this.helper.waitForEta();
  463. await expect(this.helper.execute())
  464. .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
  465. .withArgs(
  466. this.proposal.id,
  467. ProposalState.Canceled,
  468. GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
  469. );
  470. });
  471. it('cancels restricted with queueing if the same operation is part of a more recent proposal (internal)', async function () {
  472. // Set proposals
  473. const original = new GovernorHelper(this.mock, mode);
  474. await original.setProposal([this.restricted.operation], 'descr');
  475. // Go through all the governance process
  476. await original.propose();
  477. await original.waitForSnapshot();
  478. await original.connect(this.voter1).vote({ support: VoteType.For });
  479. await original.waitForDeadline();
  480. await original.queue();
  481. // Cancel the operation in the manager
  482. await this.manager
  483. .connect(this.admin)
  484. .cancel(this.mock, this.restricted.operation.target, this.restricted.operation.data);
  485. // Another proposal is added with the same operation
  486. const rescheduled = new GovernorHelper(this.mock, mode);
  487. await rescheduled.setProposal([this.restricted.operation], 'another descr');
  488. // Queue the new proposal
  489. await rescheduled.propose();
  490. await rescheduled.waitForSnapshot();
  491. await rescheduled.connect(this.voter1).vote({ support: VoteType.For });
  492. await rescheduled.waitForDeadline();
  493. await rescheduled.queue(); // This will schedule it again in the manager
  494. // Cancel
  495. const eta = await this.mock.proposalEta(rescheduled.currentProposal.id);
  496. await expect(original.cancel('internal'))
  497. .to.emit(this.mock, 'ProposalCanceled')
  498. .withArgs(original.currentProposal.id);
  499. await time.clock.timestamp().then(clock => time.increaseTo.timestamp(max(clock + 1n, eta)));
  500. await expect(original.execute())
  501. .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
  502. .withArgs(
  503. original.currentProposal.id,
  504. ProposalState.Canceled,
  505. GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
  506. );
  507. });
  508. it('cancels unrestricted with queueing (internal)', async function () {
  509. this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr');
  510. await this.helper.propose();
  511. await this.helper.waitForSnapshot();
  512. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  513. await this.helper.waitForDeadline();
  514. await this.helper.queue();
  515. const eta = await this.mock.proposalEta(this.proposal.id);
  516. await expect(this.helper.cancel('internal'))
  517. .to.emit(this.mock, 'ProposalCanceled')
  518. .withArgs(this.proposal.id);
  519. await time.clock.timestamp().then(clock => time.increaseTo.timestamp(max(clock + 1n, eta)));
  520. await expect(this.helper.execute())
  521. .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
  522. .withArgs(
  523. this.proposal.id,
  524. ProposalState.Canceled,
  525. GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
  526. );
  527. });
  528. it('cancels unrestricted without queueing (internal)', async function () {
  529. this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr');
  530. await this.helper.propose();
  531. await this.helper.waitForSnapshot();
  532. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  533. await this.helper.waitForDeadline();
  534. await expect(this.helper.cancel('internal'))
  535. .to.emit(this.mock, 'ProposalCanceled')
  536. .withArgs(this.proposal.id);
  537. await expect(this.helper.execute())
  538. .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState')
  539. .withArgs(
  540. this.proposal.id,
  541. ProposalState.Canceled,
  542. GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]),
  543. );
  544. });
  545. it('cancels calls already canceled by guardian', async function () {
  546. const operationA = { target: this.receiver.target, data: this.restricted.selector + '00' };
  547. const operationB = { target: this.receiver.target, data: this.restricted.selector + '01' };
  548. const operationC = { target: this.receiver.target, data: this.restricted.selector + '02' };
  549. const operationAId = hashOperation(this.mock.target, operationA.target, operationA.data);
  550. const operationBId = hashOperation(this.mock.target, operationB.target, operationB.data);
  551. const proposal1 = new GovernorHelper(this.mock, mode);
  552. const proposal2 = new GovernorHelper(this.mock, mode);
  553. proposal1.setProposal([operationA, operationB], 'proposal A+B');
  554. proposal2.setProposal([operationA, operationC], 'proposal A+C');
  555. for (const p of [proposal1, proposal2]) {
  556. await p.propose();
  557. await p.waitForSnapshot();
  558. await p.connect(this.voter1).vote({ support: VoteType.For });
  559. await p.waitForDeadline();
  560. }
  561. // Can queue the first proposal
  562. await proposal1.queue();
  563. // Cannot queue the second proposal: operation A already scheduled with delay
  564. await expect(proposal2.queue())
  565. .to.be.revertedWithCustomError(this.manager, 'AccessManagerAlreadyScheduled')
  566. .withArgs(operationAId);
  567. // Admin cancels operation B on the manager
  568. await this.manager.connect(this.admin).cancel(this.mock, operationB.target, operationB.data);
  569. // Still cannot queue the second proposal: operation A already scheduled with delay
  570. await expect(proposal2.queue())
  571. .to.be.revertedWithCustomError(this.manager, 'AccessManagerAlreadyScheduled')
  572. .withArgs(operationAId);
  573. await proposal1.waitForEta();
  574. // Cannot execute first proposal: operation B has been canceled
  575. await expect(proposal1.execute())
  576. .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled')
  577. .withArgs(operationBId);
  578. // Cancel the first proposal to release operation A
  579. await proposal1.cancel('internal');
  580. // can finally queue the second proposal
  581. await proposal2.queue();
  582. await proposal2.waitForEta();
  583. // Can execute second proposal
  584. await proposal2.execute();
  585. });
  586. });
  587. describe('ignore AccessManager', function () {
  588. it('defaults', async function () {
  589. expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.false;
  590. expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.true;
  591. });
  592. it('internal setter', async function () {
  593. await expect(this.mock.$_setAccessManagerIgnored(this.receiver, this.restricted.selector, true))
  594. .to.emit(this.mock, 'AccessManagerIgnoredSet')
  595. .withArgs(this.receiver, this.restricted.selector, true);
  596. expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.true;
  597. await expect(this.mock.$_setAccessManagerIgnored(this.mock, '0x12341234', false))
  598. .to.emit(this.mock, 'AccessManagerIgnoredSet')
  599. .withArgs(this.mock, '0x12341234', false);
  600. expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.false;
  601. });
  602. it('external setter', async function () {
  603. const setAccessManagerIgnored = (...args) =>
  604. this.mock.interface.encodeFunctionData('setAccessManagerIgnored', args);
  605. await this.helper.setProposal(
  606. [
  607. {
  608. target: this.mock.target,
  609. data: setAccessManagerIgnored(
  610. this.receiver.target,
  611. [this.restricted.selector, this.unrestricted.selector],
  612. true,
  613. ),
  614. },
  615. {
  616. target: this.mock.target,
  617. data: setAccessManagerIgnored(this.mock.target, ['0x12341234', '0x67896789'], false),
  618. },
  619. ],
  620. 'descr',
  621. );
  622. await this.helper.propose();
  623. await this.helper.waitForSnapshot();
  624. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  625. await this.helper.waitForDeadline();
  626. await expect(this.helper.execute()).to.emit(this.mock, 'AccessManagerIgnoredSet');
  627. expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.true;
  628. expect(await this.mock.isAccessManagerIgnored(this.receiver, this.unrestricted.selector)).to.be.true;
  629. expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.false;
  630. expect(await this.mock.isAccessManagerIgnored(this.mock, '0x67896789')).to.be.false;
  631. });
  632. it('locked function', async function () {
  633. const setAccessManagerIgnored = selector('setAccessManagerIgnored(address,bytes4[],bool)');
  634. await expect(
  635. this.mock.$_setAccessManagerIgnored(this.mock, setAccessManagerIgnored, true),
  636. ).to.be.revertedWithCustomError(this.mock, 'GovernorLockedIgnore');
  637. await this.mock.$_setAccessManagerIgnored(this.receiver, setAccessManagerIgnored, true);
  638. });
  639. it('ignores access manager', async function () {
  640. const amount = 100n;
  641. const target = this.token.target;
  642. const data = this.token.interface.encodeFunctionData('transfer', [this.voter4.address, amount]);
  643. const selector = data.slice(0, 10);
  644. await this.token.$_mint(this.mock, amount);
  645. const roleId = 1n;
  646. await this.manager.connect(this.admin).setTargetFunctionRole(target, [selector], roleId);
  647. await this.manager.connect(this.admin).grantRole(roleId, this.mock, 0);
  648. await this.helper.setProposal([{ target, data }], 'descr #1');
  649. await this.helper.propose();
  650. await this.helper.waitForSnapshot();
  651. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  652. await this.helper.waitForDeadline();
  653. await expect(this.helper.execute())
  654. .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance')
  655. .withArgs(this.manager, 0n, amount);
  656. await this.mock.$_setAccessManagerIgnored(target, selector, true);
  657. await this.helper.setProposal([{ target, data }], 'descr #2');
  658. await this.helper.propose();
  659. await this.helper.waitForSnapshot();
  660. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  661. await this.helper.waitForDeadline();
  662. await expect(this.helper.execute()).to.emit(this.token, 'Transfer').withArgs(this.mock, this.voter4, amount);
  663. });
  664. });
  665. describe('operating on an Ownable contract', function () {
  666. const method = selector('$_checkOwner()');
  667. beforeEach(async function () {
  668. this.ownable = await ethers.deployContract('$Ownable', [this.manager]);
  669. this.operation = {
  670. target: this.ownable.target,
  671. data: this.ownable.interface.encodeFunctionData('$_checkOwner'),
  672. };
  673. });
  674. it('succeeds with delay', async function () {
  675. const roleId = 1n;
  676. const executionDelay = time.duration.hours(2n);
  677. const baseDelay = time.duration.hours(1n);
  678. // Set execution delay
  679. await this.manager.connect(this.admin).setTargetFunctionRole(this.ownable, [method], roleId);
  680. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  681. // Set base delay
  682. await this.mock.$_setBaseDelaySeconds(baseDelay);
  683. await this.helper.setProposal([this.operation], `descr`);
  684. await this.helper.propose();
  685. await this.helper.waitForSnapshot();
  686. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  687. await this.helper.waitForDeadline();
  688. await this.helper.queue();
  689. await this.helper.waitForEta();
  690. await this.helper.execute(); // Don't revert
  691. });
  692. it('succeeds without delay', async function () {
  693. const roleId = 1n;
  694. const executionDelay = 0n;
  695. const baseDelay = 0n;
  696. // Set execution delay
  697. await this.manager.connect(this.admin).setTargetFunctionRole(this.ownable, [method], roleId);
  698. await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay);
  699. // Set base delay
  700. await this.mock.$_setBaseDelaySeconds(baseDelay);
  701. await this.helper.setProposal([this.operation], `descr`);
  702. await this.helper.propose();
  703. await this.helper.waitForSnapshot();
  704. await this.helper.connect(this.voter1).vote({ support: VoteType.For });
  705. await this.helper.waitForDeadline();
  706. await this.helper.execute(); // Don't revert
  707. });
  708. });
  709. });
  710. }
  711. });