diff --git a/.changeset/five-poets-mix.md b/.changeset/five-poets-mix.md new file mode 100644 index 000000000..f5050b246 --- /dev/null +++ b/.changeset/five-poets-mix.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`TimelockController`: Add the `CallSalt` event to emit on operation schedule. diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index dac55d858..615b4c0e3 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -51,6 +51,11 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver */ event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + /** + * @dev Emitted when new proposal is scheduled with non-zero salt. + */ + event CallSalt(bytes32 indexed id, bytes32 salt); + /** * @dev Emitted when operation `id` is cancelled. */ @@ -206,7 +211,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev Schedule an operation containing a single transaction. * - * Emits a {CallScheduled} event. + * Emits events {CallScheduled} and {CallSalt}. * * Requirements: * @@ -223,12 +228,15 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver bytes32 id = hashOperation(target, value, data, predecessor, salt); _schedule(id, delay); emit CallScheduled(id, 0, target, value, data, predecessor, delay); + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } } /** * @dev Schedule an operation containing a batch of transactions. * - * Emits one {CallScheduled} event per transaction in the batch. + * Emits a {CallSalt} event and one {CallScheduled} event per transaction in the batch. * * Requirements: * @@ -250,6 +258,9 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver for (uint256 i = 0; i < targets.length; ++i) { emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay); } + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } } /** diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js index 83352ef85..dde923564 100644 --- a/test/governance/TimelockController.test.js +++ b/test/governance/TimelockController.test.js @@ -158,6 +158,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 +224,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 () { @@ -364,6 +382,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); diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 4f9b9df73..34fdfe92f 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -106,6 +106,9 @@ contract('GovernorTimelockControl', function (accounts) { expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallScheduled', { id: this.proposal.timelockid }); + await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallSalt', { + id: this.proposal.timelockid, + }); expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); await expectEvent.inTransaction(txExecute.tx, this.timelock, 'CallExecuted', { id: this.proposal.timelockid });