Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a customizable _execute function in TimelockController #3317

Merged
merged 9 commits into from Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -22,6 +22,7 @@
* `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
* `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
* `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165))
* `TimelockController`: Migrate `_call` to `_execute` and allow inheritance similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317))
* `Initializable`: add a reinitializer modifier that enables the initialization of new modules, added to already initialized contracts through upgradeability. ([#3232](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3232))
* `Initializable`: add an Initialized event that tracks initialized version numbers. ([#3294](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3294))

Expand Down
46 changes: 24 additions & 22 deletions contracts/governance/TimelockController.sol
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.0;
import "../access/AccessControl.sol";
import "../token/ERC721/IERC721Receiver.sol";
import "../token/ERC1155/IERC1155Receiver.sol";
import "../utils/Address.sol";

/**
* @dev Contract module which acts as a timelocked controller. When set as the
Expand Down Expand Up @@ -288,13 +289,15 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver
function execute(
address target,
uint256 value,
bytes calldata data,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, data, predecessor, salt);
bytes32 id = hashOperation(target, value, payload, predecessor, salt);

_beforeCall(id, predecessor);
_call(id, 0, target, value, data);
_execute(target, value, payload);
emit CallExecuted(id, 0, target, value, payload);
_afterCall(id);
}

Expand All @@ -318,13 +321,30 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver
require(targets.length == payloads.length, "TimelockController: length mismatch");

bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);

_beforeCall(id, predecessor);
for (uint256 i = 0; i < targets.length; ++i) {
_call(id, i, targets[i], values[i], payloads[i]);
address target = targets[i];
uint256 value = values[i];
bytes calldata payload = payloads[i];
_execute(target, value, payload);
emit CallExecuted(id, i, target, value, payload);
}
_afterCall(id);
}

/**
* @dev Execute an operation's call.
*/
function _execute(
address target,
uint256 value,
bytes calldata data
) internal virtual {
(bool success, bytes memory returndata) = target.call{value: value}(data);
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
Address.verifyCallResult(success, returndata, "TimelockController: underlying transaction reverted");
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @dev Checks before execution of an operation's calls.
*/
Expand All @@ -341,24 +361,6 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver
_timestamps[id] = _DONE_TIMESTAMP;
}

/**
* @dev Execute an operation's call.
*
* Emits a {CallExecuted} event.
*/
function _call(
bytes32 id,
uint256 index,
address target,
uint256 value,
bytes calldata data
) private {
(bool success, ) = target.call{value: value}(data);
require(success, "TimelockController: underlying transaction reverted");

emit CallExecuted(id, index, target, value, data);
}

/**
* @dev Changes the minimum timelock duration for future operations.
*
Expand Down
40 changes: 35 additions & 5 deletions test/governance/TimelockController.test.js
Expand Up @@ -634,7 +634,7 @@ contract('TimelockController', function (accounts) {
],
[
this.callreceivermock.contract.methods.mockFunction().encodeABI(),
this.callreceivermock.contract.methods.mockFunctionThrows().encodeABI(),
this.callreceivermock.contract.methods.mockFunctionRevertsNoReason().encodeABI(),
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
this.callreceivermock.contract.methods.mockFunction().encodeABI(),
],
ZERO_BYTES32,
Expand Down Expand Up @@ -886,6 +886,38 @@ contract('TimelockController', function (accounts) {
);
});

it('call reverting with message', async function () {
const operation = genOperation(
this.callreceivermock.address,
0,
this.callreceivermock.contract.methods.mockFunctionRevertsReason().encodeABI(),
ZERO_BYTES32,
'0xb1b1b276fdf1a28d1e00537ea73b04d56639128b08063c1a2f70a52e38cba693',
);

await this.mock.schedule(
operation.target,
operation.value,
operation.data,
operation.predecessor,
operation.salt,
MINDELAY,
{ from: proposer },
);
await time.increase(MINDELAY);
await expectRevert(
this.mock.execute(
operation.target,
operation.value,
operation.data,
operation.predecessor,
operation.salt,
{ from: executor },
),
'CallReceiverMock: reverting',
);
});

Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
it('call throw', async function () {
const operation = genOperation(
this.callreceivermock.address,
Expand All @@ -905,7 +937,7 @@ contract('TimelockController', function (accounts) {
{ from: proposer },
);
await time.increase(MINDELAY);
await expectRevert(
await expectRevert.unspecified(
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
this.mock.execute(
operation.target,
operation.value,
Expand All @@ -914,7 +946,6 @@ contract('TimelockController', function (accounts) {
operation.salt,
{ from: executor },
),
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
'TimelockController: underlying transaction reverted',
);
});

Expand All @@ -937,7 +968,7 @@ contract('TimelockController', function (accounts) {
{ from: proposer },
);
await time.increase(MINDELAY);
await expectRevert(
await expectRevert.outOfGas(
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
this.mock.execute(
operation.target,
operation.value,
Expand All @@ -946,7 +977,6 @@ contract('TimelockController', function (accounts) {
operation.salt,
{ from: executor, gas: '70000' },
),
Joeysantoro marked this conversation as resolved.
Show resolved Hide resolved
'TimelockController: underlying transaction reverted',
);
});

Expand Down