Merge branch 'master' into next-v5.0

This commit is contained in:
Francisco Giordano
2023-05-16 00:07:07 -03:00
308 changed files with 21085 additions and 11515 deletions

View File

@ -10,6 +10,7 @@ const CallReceiverMock = artifacts.require('CallReceiverMock');
const Implementation2 = artifacts.require('Implementation2');
const ERC721 = artifacts.require('$ERC721');
const ERC1155 = artifacts.require('$ERC1155');
const TimelockReentrant = artifacts.require('$TimelockReentrant');
const MINDELAY = time.duration.days(1);
@ -158,6 +159,11 @@ contract('TimelockController', function (accounts) {
delay: MINDELAY,
});
expectEvent(receipt, 'CallSalt', {
id: this.operation.id,
salt: this.operation.salt,
});
const block = await web3.eth.getBlock(receipt.receipt.blockHash);
expect(await this.mock.getTimestamp(this.operation.id)).to.be.bignumber.equal(
@ -219,6 +225,19 @@ contract('TimelockController', function (accounts) {
'TimelockController: insufficient delay',
);
});
it('schedule operation with salt zero', async function () {
const { receipt } = await this.mock.schedule(
this.operation.target,
this.operation.value,
this.operation.data,
this.operation.predecessor,
ZERO_BYTES32,
MINDELAY,
{ from: proposer },
);
expectEvent.notEmitted(receipt, 'CallSalt');
});
});
describe('execute', function () {
@ -327,6 +346,82 @@ contract('TimelockController', function (accounts) {
`AccessControl: account ${other.toLowerCase()} is missing role ${EXECUTOR_ROLE}`,
);
});
it('prevents reentrancy execution', async function () {
// Create operation
const reentrant = await TimelockReentrant.new();
const reentrantOperation = genOperation(
reentrant.address,
0,
reentrant.contract.methods.reenter().encodeABI(),
ZERO_BYTES32,
salt,
);
// Schedule so it can be executed
await this.mock.schedule(
reentrantOperation.target,
reentrantOperation.value,
reentrantOperation.data,
reentrantOperation.predecessor,
reentrantOperation.salt,
MINDELAY,
{ from: proposer },
);
// Advance on time to make the operation executable
const timestamp = await this.mock.getTimestamp(reentrantOperation.id);
await time.increaseTo(timestamp);
// Grant executor role to the reentrant contract
await this.mock.grantRole(EXECUTOR_ROLE, reentrant.address, { from: admin });
// Prepare reenter
const data = this.mock.contract.methods
.execute(
reentrantOperation.target,
reentrantOperation.value,
reentrantOperation.data,
reentrantOperation.predecessor,
reentrantOperation.salt,
)
.encodeABI();
await reentrant.enableRentrancy(this.mock.address, data);
// Expect to fail
await expectRevert(
this.mock.execute(
reentrantOperation.target,
reentrantOperation.value,
reentrantOperation.data,
reentrantOperation.predecessor,
reentrantOperation.salt,
{ from: executor },
),
'TimelockController: operation is not ready',
);
// Disable reentrancy
await reentrant.disableReentrancy();
const nonReentrantOperation = reentrantOperation; // Not anymore
// Try again successfully
const receipt = await this.mock.execute(
nonReentrantOperation.target,
nonReentrantOperation.value,
nonReentrantOperation.data,
nonReentrantOperation.predecessor,
nonReentrantOperation.salt,
{ from: executor },
);
expectEvent(receipt, 'CallExecuted', {
id: nonReentrantOperation.id,
index: web3.utils.toBN(0),
target: nonReentrantOperation.target,
value: web3.utils.toBN(nonReentrantOperation.value),
data: nonReentrantOperation.data,
});
});
});
});
});
@ -364,6 +459,11 @@ contract('TimelockController', function (accounts) {
predecessor: this.operation.predecessor,
delay: MINDELAY,
});
expectEvent(receipt, 'CallSalt', {
id: this.operation.id,
salt: this.operation.salt,
});
}
const block = await web3.eth.getBlock(receipt.receipt.blockHash);
@ -609,6 +709,84 @@ contract('TimelockController', function (accounts) {
'TimelockController: length mismatch',
);
});
it('prevents reentrancy execution', async function () {
// Create operation
const reentrant = await TimelockReentrant.new();
const reentrantBatchOperation = genOperationBatch(
[reentrant.address],
[0],
[reentrant.contract.methods.reenter().encodeABI()],
ZERO_BYTES32,
salt,
);
// Schedule so it can be executed
await this.mock.scheduleBatch(
reentrantBatchOperation.targets,
reentrantBatchOperation.values,
reentrantBatchOperation.payloads,
reentrantBatchOperation.predecessor,
reentrantBatchOperation.salt,
MINDELAY,
{ from: proposer },
);
// Advance on time to make the operation executable
const timestamp = await this.mock.getTimestamp(reentrantBatchOperation.id);
await time.increaseTo(timestamp);
// Grant executor role to the reentrant contract
await this.mock.grantRole(EXECUTOR_ROLE, reentrant.address, { from: admin });
// Prepare reenter
const data = this.mock.contract.methods
.executeBatch(
reentrantBatchOperation.targets,
reentrantBatchOperation.values,
reentrantBatchOperation.payloads,
reentrantBatchOperation.predecessor,
reentrantBatchOperation.salt,
)
.encodeABI();
await reentrant.enableRentrancy(this.mock.address, data);
// Expect to fail
await expectRevert(
this.mock.executeBatch(
reentrantBatchOperation.targets,
reentrantBatchOperation.values,
reentrantBatchOperation.payloads,
reentrantBatchOperation.predecessor,
reentrantBatchOperation.salt,
{ from: executor },
),
'TimelockController: operation is not ready',
);
// Disable reentrancy
await reentrant.disableReentrancy();
const nonReentrantBatchOperation = reentrantBatchOperation; // Not anymore
// Try again successfully
const receipt = await this.mock.executeBatch(
nonReentrantBatchOperation.targets,
nonReentrantBatchOperation.values,
nonReentrantBatchOperation.payloads,
nonReentrantBatchOperation.predecessor,
nonReentrantBatchOperation.salt,
{ from: executor },
);
for (const i in nonReentrantBatchOperation.targets) {
expectEvent(receipt, 'CallExecuted', {
id: nonReentrantBatchOperation.id,
index: web3.utils.toBN(i),
target: nonReentrantBatchOperation.targets[i],
value: web3.utils.toBN(nonReentrantBatchOperation.values[i]),
data: nonReentrantBatchOperation.payloads[i],
});
}
});
});
});