Improve GovernorTimelockAccess (#4591)

Co-authored-by: Francisco <fg@frang.io>
This commit is contained in:
Hadrien Croubois
2023-09-14 01:25:35 +02:00
committed by GitHub
parent 75eb7c2d49
commit 80b2d1df38
4 changed files with 317 additions and 87 deletions

View File

@ -5,6 +5,7 @@ const Enums = require('../../helpers/enums');
const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance');
const { expectRevertCustomError } = require('../../helpers/customError');
const { clockFromReceipt } = require('../../helpers/time');
const { selector } = require('../../helpers/methods');
const AccessManager = artifacts.require('$AccessManager');
const Governor = artifacts.require('$GovernorTimelockAccessMock');
@ -210,36 +211,196 @@ contract('GovernorTimelockAccess', function (accounts) {
await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledUnrestricted');
});
it('cancellation after queue (internal)', async function () {
describe('cancel', function () {
const delay = 1000;
const roleId = '1';
await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, {
from: admin,
});
await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin });
this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
await this.helper.waitForDeadline();
await this.helper.queue();
const txCancel = await this.helper.cancel('internal');
expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id });
await expectEvent.inTransaction(txCancel.tx, this.manager, 'OperationCanceled', {
operationId: this.restricted.operationId,
nonce: '1',
beforeEach(async function () {
await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, {
from: admin,
});
await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin });
});
await this.helper.waitForEta();
await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
this.proposal.id,
Enums.ProposalState.Canceled,
proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
]);
it('cancellation after queue (internal)', async function () {
this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr');
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
await this.helper.waitForDeadline();
await this.helper.queue();
const txCancel = await this.helper.cancel('internal');
expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id });
await expectEvent.inTransaction(txCancel.tx, this.manager, 'OperationCanceled', {
operationId: this.restricted.operationId,
nonce: '1',
});
await this.helper.waitForEta();
await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [
this.proposal.id,
Enums.ProposalState.Canceled,
proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]),
]);
});
it('cancel calls already canceled by guardian', async function () {
const operationA = { target: this.receiver.address, data: this.restricted.selector + '00' };
const operationB = { target: this.receiver.address, data: this.restricted.selector + '01' };
const operationC = { target: this.receiver.address, data: this.restricted.selector + '02' };
const operationAId = hashOperation(this.mock.address, operationA.target, operationA.data);
const operationBId = hashOperation(this.mock.address, operationB.target, operationB.data);
const proposal1 = new GovernorHelper(this.mock, mode);
const proposal2 = new GovernorHelper(this.mock, mode);
proposal1.setProposal([operationA, operationB], 'proposal A+B');
proposal2.setProposal([operationA, operationC], 'proposal A+C');
for (const p of [proposal1, proposal2]) {
await p.propose();
await p.waitForSnapshot();
await p.vote({ support: Enums.VoteType.For }, { from: voter1 });
await p.waitForDeadline();
}
// Can queue the first proposal
await proposal1.queue();
// Cannot queue the second proposal: operation A already scheduled with delay
await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]);
// Admin cancels operation B on the manager
await this.manager.cancel(this.mock.address, operationB.target, operationB.data, { from: admin });
// Still cannot queue the second proposal: operation A already scheduled with delay
await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]);
await proposal1.waitForEta();
// Cannot execute first proposal: operation B has been canceled
await expectRevertCustomError(proposal1.execute(), 'AccessManagerNotScheduled', [operationBId]);
// Cancel the first proposal to release operation A
await proposal1.cancel('internal');
// can finally queue the second proposal
await proposal2.queue();
await proposal2.waitForEta();
// Can execute second proposal
await proposal2.execute();
});
});
describe('ignore AccessManager', function () {
it('defaults', async function () {
expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal(
false,
);
expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(true);
});
it('internal setter', async function () {
const p1 = { target: this.receiver.address, selector: this.restricted.selector, ignored: true };
const tx1 = await this.mock.$_setAccessManagerIgnored(p1.target, p1.selector, p1.ignored);
expect(await this.mock.isAccessManagerIgnored(p1.target, p1.selector)).to.equal(p1.ignored);
expectEvent(tx1, 'AccessManagerIgnoredSet', p1);
const p2 = { target: this.mock.address, selector: '0x12341234', ignored: false };
const tx2 = await this.mock.$_setAccessManagerIgnored(p2.target, p2.selector, p2.ignored);
expect(await this.mock.isAccessManagerIgnored(p2.target, p2.selector)).to.equal(p2.ignored);
expectEvent(tx2, 'AccessManagerIgnoredSet', p2);
});
it('external setter', async function () {
const setAccessManagerIgnored = (...args) =>
this.mock.contract.methods.setAccessManagerIgnored(...args).encodeABI();
await this.helper.setProposal(
[
{
target: this.mock.address,
data: setAccessManagerIgnored(
this.receiver.address,
[this.restricted.selector, this.unrestricted.selector],
true,
),
value: '0',
},
{
target: this.mock.address,
data: setAccessManagerIgnored(this.mock.address, ['0x12341234', '0x67896789'], false),
value: '0',
},
],
'descr',
);
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
await this.helper.waitForDeadline();
const tx = await this.helper.execute();
expectEvent(tx, 'AccessManagerIgnoredSet');
expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal(
true,
);
expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.unrestricted.selector)).to.equal(
true,
);
expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(false);
expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x67896789')).to.equal(false);
});
it('locked function', async function () {
const setAccessManagerIgnored = selector('setAccessManagerIgnored(address,bytes4[],bool)');
await expectRevertCustomError(
this.mock.$_setAccessManagerIgnored(this.mock.address, setAccessManagerIgnored, true),
'GovernorLockedIgnore',
[],
);
await this.mock.$_setAccessManagerIgnored(this.receiver.address, setAccessManagerIgnored, true);
});
it('ignores access manager', async function () {
const amount = 100;
const target = this.token.address;
const data = this.token.contract.methods.transfer(voter4, amount).encodeABI();
const selector = data.slice(0, 10);
await this.token.$_mint(this.mock.address, amount);
const roleId = '1';
await this.manager.setTargetFunctionRole(target, [selector], roleId, { from: admin });
await this.manager.grantRole(roleId, this.mock.address, 0, { from: admin });
await this.helper.setProposal([{ target, data, value: '0' }], '1');
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
await this.helper.waitForDeadline();
await expectRevertCustomError(this.helper.execute(), 'ERC20InsufficientBalance', [
this.manager.address,
0,
amount,
]);
await this.mock.$_setAccessManagerIgnored(target, selector, true);
await this.helper.setProposal([{ target, data, value: '0' }], '2');
await this.helper.propose();
await this.helper.waitForSnapshot();
await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 });
await this.helper.waitForDeadline();
const tx = await this.helper.execute();
expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.mock.address });
});
});
});
}