From 91c8d32f2bc29c12f1ccc9818802e1cf77b1f505 Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Thu, 4 Sep 2025 12:30:08 -0300 Subject: [PATCH 01/28] Fix https://github.com/OpenZeppelin/openzeppelin-community-contracts/issues/213 Fixes https://github.com/OpenZeppelin/openzeppelin-community-contracts/issues/213 --- .../TimelockControllerEnumerable.sol | 185 ++++++++++++++++++ .../TimelockControllerEnumerable.t.sol | 173 ++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 contracts/governance/TimelockControllerEnumerable.sol create mode 100644 test/governance/TimelockControllerEnumerable.t.sol diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol new file mode 100644 index 00000000..e8dd300a --- /dev/null +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +contract TimelockControllerEnumerable is TimelockController { + using EnumerableSet for EnumerableSet.Bytes32Set; + + struct Operation { + address target; + uint256 value; + bytes data; + bytes32 predecessor; + bytes32 salt; + uint256 delay; + } + + struct OperationBatch { + address[] targets; + uint256[] values; + bytes[] payloads; + bytes32 predecessor; + bytes32 salt; + uint256 delay; + } + + error OperationIndexNotFound(uint256 index); + error OperationIdNotFound(bytes32 id); + error OperationBatchIndexNotFound(uint256 index); + error OperationBatchIdNotFound(bytes32 id); + + EnumerableSet.Bytes32Set private _operationsIdSet; + mapping(bytes32 id => Operation operation) private _operationsMap; + + EnumerableSet.Bytes32Set private _operationsBatchIdSet; + mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; + + constructor( + uint256 minDelay, + address[] memory proposers, + address[] memory executors, + address canceller + ) TimelockController(minDelay, proposers, executors, canceller) {} + + /// @inheritdoc TimelockController + /// @dev Store the operation the mapping and set + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual override { + super.schedule(target, value, data, predecessor, salt, delay); + bytes32 id = hashOperation(target, value, data, predecessor, salt); + _operationsIdSet.add(id); + _operationsMap[id] = Operation({ + target: target, + value: value, + data: data, + predecessor: predecessor, + salt: salt, + delay: delay + }); + } + + /// @inheritdoc TimelockController + /// @dev Store the operationBatch the mapping and set + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual override { + super.scheduleBatch(targets, values, payloads, predecessor, salt, delay); + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + _operationsBatchIdSet.add(id); + _operationsBatchMap[id] = OperationBatch({ + targets: targets, + values: values, + payloads: payloads, + predecessor: predecessor, + salt: salt, + delay: delay + }); + } + + /// @inheritdoc TimelockController + /// @dev Remove the operation from the mapping and set + function cancel(bytes32 id) public virtual override { + super.cancel(id); + if (_operationsIdSet.contains(id)) { + _operationsIdSet.remove(id); + delete _operationsMap[id]; + } + if (_operationsBatchIdSet.contains(id)) { + _operationsBatchIdSet.remove(id); + delete _operationsBatchMap[id]; + } + } + + /// @dev Return the operations from the mapping and set + /// @return operations_ The operations array + function operations() public view returns (Operation[] memory operations_) { + uint256 operationsCount_ = _operationsIdSet.length(); + operations_ = new Operation[](operationsCount_); + for (uint256 i = 0; i < operationsCount_; i++) { + operations_[i] = _operationsMap[_operationsIdSet.at(i)]; + } + return operations_; + } + + /// @dev Return the number of operations from the set + /// @return operationsCount_ The number of operations + function operationsCount() public view returns (uint256 operationsCount_) { + operationsCount_ = _operationsIdSet.length(); + return operationsCount_; + } + + /// @dev Return the operation at the given index from the mapping and set + /// @param index The index of the operation + /// @return operation_ The operation + function operation(uint256 index) public view returns (Operation memory operation_) { + if (index >= _operationsIdSet.length()) { + revert OperationIndexNotFound(index); + } + operation_ = _operationsMap[_operationsIdSet.at(index)]; + return operation_; + } + + /// @dev Return the operation with the given id from the mapping and set + /// @param id The id of the operation + /// @return operation_ The operation + function operation(bytes32 id) public view returns (Operation memory operation_) { + if (!_operationsIdSet.contains(id)) { + revert OperationIdNotFound(id); + } + operation_ = _operationsMap[id]; + return operation_; + } + + /// @dev Return the operationsBatch from the mapping and set + /// @return operationsBatch_ The operationsBatch array + function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { + uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); + operationsBatch_ = new OperationBatch[](operationsBatchCount_); + for (uint256 i = 0; i < operationsBatchCount_; i++) { + operationsBatch_[i] = _operationsBatchMap[_operationsBatchIdSet.at(i)]; + } + return operationsBatch_; + } + + /// @dev Return the number of operationsBatch from the set + /// @return operationsBatchCount_ The number of operationsBatch + function operationsBatchCount() public view returns (uint256 operationsBatchCount_) { + operationsBatchCount_ = _operationsBatchIdSet.length(); + return operationsBatchCount_; + } + + /// @dev Return the operationsBatch at the given index from the mapping and set + /// @param index The index of the operationsBatch + /// @return operationBatch_ The operationsBatch + function operationBatch(uint256 index) public view returns (OperationBatch memory operationBatch_) { + if (index >= _operationsBatchIdSet.length()) { + revert OperationBatchIndexNotFound(index); + } + operationBatch_ = _operationsBatchMap[_operationsBatchIdSet.at(index)]; + return operationBatch_; + } + + /// @dev Return the operationsBatch with the given id from the mapping and set + /// @param id The id of the operationsBatch + /// @return operationBatch_ The operationsBatch + function operationBatch(bytes32 id) public view returns (OperationBatch memory operationBatch_) { + if (!_operationsBatchIdSet.contains(id)) { + revert OperationBatchIdNotFound(id); + } + operationBatch_ = _operationsBatchMap[id]; + return operationBatch_; + } +} diff --git a/test/governance/TimelockControllerEnumerable.t.sol b/test/governance/TimelockControllerEnumerable.t.sol new file mode 100644 index 00000000..a4282bfb --- /dev/null +++ b/test/governance/TimelockControllerEnumerable.t.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import { + TimelockControllerEnumerable +} from "@openzeppelin/community-contracts/governance/TimelockControllerEnumerable.sol"; + +contract TimelockControllerEnumerableTest is Test { + TimelockControllerEnumerable public timelockControllerEnumerable; + + event Call(); + + function setUp() public { + address[] memory proposers = new address[](1); + address[] memory executors = new address[](1); + proposers[0] = address(this); + executors[0] = address(this); + uint256 minDelay = 1 days; + timelockControllerEnumerable = new TimelockControllerEnumerable(minDelay, proposers, executors, address(0)); + } + + function call() external { + emit Call(); + } + + function test_schedule() public { + timelockControllerEnumerable.schedule( + address(this), + 0, + abi.encodeCall(this.call, ()), + bytes32(0), + bytes32(0), + 1 days + ); + assertEq(timelockControllerEnumerable.operationsCount(), 1); + TimelockControllerEnumerable.Operation memory operation = timelockControllerEnumerable.operation(uint256(0)); + assertEq(operation.target, address(this)); + assertEq(operation.value, 0); + assertEq(operation.data, abi.encodeCall(this.call, ())); + assertEq(operation.predecessor, bytes32(0)); + assertEq(operation.salt, bytes32(0)); + assertEq(operation.delay, 1 days); + bytes32 id = timelockControllerEnumerable.hashOperation( + address(this), + 0, + abi.encodeCall(this.call, ()), + bytes32(0), + bytes32(0) + ); + operation = timelockControllerEnumerable.operation(id); + assertEq(operation.target, address(this)); + assertEq(operation.value, 0); + assertEq(operation.data, abi.encodeCall(this.call, ())); + assertEq(operation.predecessor, bytes32(0)); + assertEq(operation.salt, bytes32(0)); + assertEq(operation.delay, 1 days); + } + + function test_schedule_execute() public { + test_schedule(); + TimelockControllerEnumerable.Operation memory operation = timelockControllerEnumerable.operation(uint256(0)); + bytes32 id = timelockControllerEnumerable.hashOperation( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt + ); + assertEq(timelockControllerEnumerable.isOperationPending(id), true); + vm.warp(block.timestamp + operation.delay); + timelockControllerEnumerable.execute( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt + ); + assertEq(timelockControllerEnumerable.isOperationPending(id), false); + } + + function test_scheduleBatch() public { + address[] memory targets = new address[](1); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + targets[0] = address(this); + values[0] = 0; + payloads[0] = abi.encodeCall(this.call, ()); + timelockControllerEnumerable.scheduleBatch(targets, values, payloads, bytes32(0), bytes32(0), 1 days); + assertEq(timelockControllerEnumerable.operationsBatchCount(), 1); + TimelockControllerEnumerable.OperationBatch memory operationBatch = timelockControllerEnumerable.operationBatch( + uint256(0) + ); + assertEq(operationBatch.targets[0], address(this)); + assertEq(operationBatch.values[0], 0); + assertEq(operationBatch.payloads[0], abi.encodeCall(this.call, ())); + assertEq(operationBatch.predecessor, bytes32(0)); + assertEq(operationBatch.salt, bytes32(0)); + assertEq(operationBatch.delay, 1 days); + bytes32 id = timelockControllerEnumerable.hashOperationBatch(targets, values, payloads, bytes32(0), bytes32(0)); + operationBatch = timelockControllerEnumerable.operationBatch(id); + assertEq(operationBatch.targets[0], address(this)); + assertEq(operationBatch.values[0], 0); + assertEq(operationBatch.payloads[0], abi.encodeCall(this.call, ())); + assertEq(operationBatch.predecessor, bytes32(0)); + assertEq(operationBatch.salt, bytes32(0)); + assertEq(operationBatch.delay, 1 days); + } + + function test_scheduleBatch_execute() public { + test_scheduleBatch(); + TimelockControllerEnumerable.OperationBatch memory operationBatch = timelockControllerEnumerable.operationBatch( + uint256(0) + ); + bytes32 id = timelockControllerEnumerable.hashOperationBatch( + operationBatch.targets, + operationBatch.values, + operationBatch.payloads, + operationBatch.predecessor, + operationBatch.salt + ); + assertEq(timelockControllerEnumerable.isOperationPending(id), true); + vm.warp(block.timestamp + operationBatch.delay); + timelockControllerEnumerable.executeBatch( + operationBatch.targets, + operationBatch.values, + operationBatch.payloads, + operationBatch.predecessor, + operationBatch.salt + ); + assertEq(timelockControllerEnumerable.isOperationPending(id), false); + } + + function test_cancel_schedule() public { + timelockControllerEnumerable.schedule( + address(this), + 0, + abi.encodeCall(this.call, ()), + bytes32(0), + bytes32(0), + 1 days + ); + assertEq(timelockControllerEnumerable.operationsCount(), 1); + bytes32 id = timelockControllerEnumerable.hashOperation( + address(this), + 0, + abi.encodeCall(this.call, ()), + bytes32(0), + bytes32(0) + ); + timelockControllerEnumerable.cancel(id); + assertEq(timelockControllerEnumerable.operationsCount(), 0); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationIdNotFound.selector, id)); + timelockControllerEnumerable.operation(id); + } + + function test_cancel_scheduleBatch() public { + address[] memory targets = new address[](1); + uint256[] memory values = new uint256[](1); + bytes[] memory payloads = new bytes[](1); + targets[0] = address(this); + values[0] = 0; + payloads[0] = abi.encodeCall(this.call, ()); + timelockControllerEnumerable.scheduleBatch(targets, values, payloads, bytes32(0), bytes32(0), 1 days); + assertEq(timelockControllerEnumerable.operationsBatchCount(), 1); + bytes32 id = timelockControllerEnumerable.hashOperationBatch(targets, values, payloads, bytes32(0), bytes32(0)); + timelockControllerEnumerable.cancel(id); + assertEq(timelockControllerEnumerable.operationsBatchCount(), 0); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationBatchIdNotFound.selector, id)); + timelockControllerEnumerable.operationBatch(id); + } +} From d7155124d257a0f3616d16544495668dba9eb09f Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Thu, 4 Sep 2025 12:33:16 -0300 Subject: [PATCH 02/28] Improve NatSpec --- .../governance/TimelockControllerEnumerable.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index e8dd300a..754d2f4c 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -4,9 +4,12 @@ pragma solidity ^0.8.20; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +/// @inheritdoc TimelockController +/// @dev Extends the TimelockController to allow for enumerable operations contract TimelockControllerEnumerable is TimelockController { using EnumerableSet for EnumerableSet.Bytes32Set; + /// @notice The operation struct struct Operation { address target; uint256 value; @@ -16,6 +19,7 @@ contract TimelockControllerEnumerable is TimelockController { uint256 delay; } + /// @notice The operation batch struct struct OperationBatch { address[] targets; uint256[] values; @@ -25,17 +29,26 @@ contract TimelockControllerEnumerable is TimelockController { uint256 delay; } + /// @notice The error when the operation index is not found error OperationIndexNotFound(uint256 index); + /// @notice The error when the operation id is not found error OperationIdNotFound(bytes32 id); + /// @notice The error when the operation batch index is not found error OperationBatchIndexNotFound(uint256 index); + /// @notice The error when the operation batch id is not found error OperationBatchIdNotFound(bytes32 id); + /// @notice The operations id set EnumerableSet.Bytes32Set private _operationsIdSet; + /// @notice The operations map mapping(bytes32 id => Operation operation) private _operationsMap; + /// @notice The operations batch id set EnumerableSet.Bytes32Set private _operationsBatchIdSet; + /// @notice The operations batch map mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; + /// @inheritdoc TimelockController constructor( uint256 minDelay, address[] memory proposers, From a0144bf2a08a43e82bafd3270cbe2034f103710e Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Thu, 4 Sep 2025 12:35:31 -0300 Subject: [PATCH 03/28] Update tests --- contracts/governance/TimelockControllerEnumerable.sol | 2 -- test/governance/TimelockControllerEnumerable.t.sol | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 754d2f4c..dd577a84 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.20; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -/// @inheritdoc TimelockController /// @dev Extends the TimelockController to allow for enumerable operations contract TimelockControllerEnumerable is TimelockController { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -48,7 +47,6 @@ contract TimelockControllerEnumerable is TimelockController { /// @notice The operations batch map mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; - /// @inheritdoc TimelockController constructor( uint256 minDelay, address[] memory proposers, diff --git a/test/governance/TimelockControllerEnumerable.t.sol b/test/governance/TimelockControllerEnumerable.t.sol index a4282bfb..eb751b87 100644 --- a/test/governance/TimelockControllerEnumerable.t.sol +++ b/test/governance/TimelockControllerEnumerable.t.sol @@ -153,6 +153,8 @@ contract TimelockControllerEnumerableTest is Test { assertEq(timelockControllerEnumerable.operationsCount(), 0); vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationIdNotFound.selector, id)); timelockControllerEnumerable.operation(id); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationIndexNotFound.selector, 0)); + timelockControllerEnumerable.operation(uint256(0)); } function test_cancel_scheduleBatch() public { @@ -169,5 +171,7 @@ contract TimelockControllerEnumerableTest is Test { assertEq(timelockControllerEnumerable.operationsBatchCount(), 0); vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationBatchIdNotFound.selector, id)); timelockControllerEnumerable.operationBatch(id); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.OperationBatchIndexNotFound.selector, 0)); + timelockControllerEnumerable.operationBatch(uint256(0)); } } From f52ccdda5be45b6ab9ef6285035dceea06614f50 Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Thu, 4 Sep 2025 12:46:52 -0300 Subject: [PATCH 04/28] Update docs --- .../TimelockControllerEnumerable.sol | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index dd577a84..1876bd94 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -51,11 +51,11 @@ contract TimelockControllerEnumerable is TimelockController { uint256 minDelay, address[] memory proposers, address[] memory executors, - address canceller - ) TimelockController(minDelay, proposers, executors, canceller) {} + address admin + ) TimelockController(minDelay, proposers, executors, admin) {} /// @inheritdoc TimelockController - /// @dev Store the operation the mapping and set + /// @dev Store the operation function schedule( address target, uint256 value, @@ -78,7 +78,7 @@ contract TimelockControllerEnumerable is TimelockController { } /// @inheritdoc TimelockController - /// @dev Store the operationBatch the mapping and set + /// @dev Store the operationBatch function scheduleBatch( address[] calldata targets, uint256[] calldata values, @@ -101,7 +101,7 @@ contract TimelockControllerEnumerable is TimelockController { } /// @inheritdoc TimelockController - /// @dev Remove the operation from the mapping and set + /// @dev Remove the operation function cancel(bytes32 id) public virtual override { super.cancel(id); if (_operationsIdSet.contains(id)) { @@ -114,7 +114,7 @@ contract TimelockControllerEnumerable is TimelockController { } } - /// @dev Return the operations from the mapping and set + /// @dev Return the operations /// @return operations_ The operations array function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); @@ -132,7 +132,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationsCount_; } - /// @dev Return the operation at the given index from the mapping and set + /// @dev Return the operation at the given index /// @param index The index of the operation /// @return operation_ The operation function operation(uint256 index) public view returns (Operation memory operation_) { @@ -143,7 +143,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @dev Return the operation with the given id from the mapping and set + /// @dev Return the operation with the given id /// @param id The id of the operation /// @return operation_ The operation function operation(bytes32 id) public view returns (Operation memory operation_) { @@ -154,7 +154,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @dev Return the operationsBatch from the mapping and set + /// @dev Return the operationsBatch /// @return operationsBatch_ The operationsBatch array function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); @@ -172,7 +172,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationsBatchCount_; } - /// @dev Return the operationsBatch at the given index from the mapping and set + /// @dev Return the operationsBatch at the given index /// @param index The index of the operationsBatch /// @return operationBatch_ The operationsBatch function operationBatch(uint256 index) public view returns (OperationBatch memory operationBatch_) { @@ -183,7 +183,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationBatch_; } - /// @dev Return the operationsBatch with the given id from the mapping and set + /// @dev Return the operationsBatch with the given id /// @param id The id of the operationsBatch /// @return operationBatch_ The operationsBatch function operationBatch(bytes32 id) public view returns (OperationBatch memory operationBatch_) { From cc6c8ec33194ced438cfbe9fb61bbc06212e0b38 Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Mon, 8 Sep 2025 21:31:19 -0300 Subject: [PATCH 05/28] Update docs --- contracts/governance/README.adoc | 12 +++++++++ .../TimelockControllerEnumerable.sol | 26 ++++++++++--------- 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 contracts/governance/README.adoc diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc new file mode 100644 index 00000000..f8aa55da --- /dev/null +++ b/contracts/governance/README.adoc @@ -0,0 +1,12 @@ += Governance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/governance + +This directory includes extensions and utilities for on-chain governance. + +* {TimelockControllerEnumerable}: Extension of OpenZeppelin's TimelockController with enumerable operations support. + +== Timelock + +{{TimelockControllerEnumerable}} \ No newline at end of file diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 1876bd94..c6954cfa 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -/// @dev Extends the TimelockController to allow for enumerable operations +/// @notice Extends the TimelockController to allow for enumerable operations contract TimelockControllerEnumerable is TimelockController { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -47,6 +47,11 @@ contract TimelockControllerEnumerable is TimelockController { /// @notice The operations batch map mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; + /// @notice Initializes the contract with the given timelock parameters + /// @param minDelay initial minimum delay in seconds for operations + /// @param proposers accounts to be granted proposer and canceller roles + /// @param executors accounts to be granted executor role + /// @param admin optional account to be granted admin role; disable with zero address constructor( uint256 minDelay, address[] memory proposers, @@ -55,7 +60,6 @@ contract TimelockControllerEnumerable is TimelockController { ) TimelockController(minDelay, proposers, executors, admin) {} /// @inheritdoc TimelockController - /// @dev Store the operation function schedule( address target, uint256 value, @@ -78,7 +82,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @inheritdoc TimelockController - /// @dev Store the operationBatch function scheduleBatch( address[] calldata targets, uint256[] calldata values, @@ -101,7 +104,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @inheritdoc TimelockController - /// @dev Remove the operation function cancel(bytes32 id) public virtual override { super.cancel(id); if (_operationsIdSet.contains(id)) { @@ -114,7 +116,7 @@ contract TimelockControllerEnumerable is TimelockController { } } - /// @dev Return the operations + /// @notice Return all scheduled operations /// @return operations_ The operations array function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); @@ -125,14 +127,14 @@ contract TimelockControllerEnumerable is TimelockController { return operations_; } - /// @dev Return the number of operations from the set + /// @notice Return the number of operations from the set /// @return operationsCount_ The number of operations function operationsCount() public view returns (uint256 operationsCount_) { operationsCount_ = _operationsIdSet.length(); return operationsCount_; } - /// @dev Return the operation at the given index + /// @notice Return the operation at the given index /// @param index The index of the operation /// @return operation_ The operation function operation(uint256 index) public view returns (Operation memory operation_) { @@ -143,7 +145,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @dev Return the operation with the given id + /// @notice Return the operation with the given id /// @param id The id of the operation /// @return operation_ The operation function operation(bytes32 id) public view returns (Operation memory operation_) { @@ -154,7 +156,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @dev Return the operationsBatch + /// @notice Return all scheduled operation batches /// @return operationsBatch_ The operationsBatch array function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); @@ -165,14 +167,14 @@ contract TimelockControllerEnumerable is TimelockController { return operationsBatch_; } - /// @dev Return the number of operationsBatch from the set + /// @notice Return the number of operationsBatch from the set /// @return operationsBatchCount_ The number of operationsBatch function operationsBatchCount() public view returns (uint256 operationsBatchCount_) { operationsBatchCount_ = _operationsBatchIdSet.length(); return operationsBatchCount_; } - /// @dev Return the operationsBatch at the given index + /// @notice Return the operationsBatch at the given index /// @param index The index of the operationsBatch /// @return operationBatch_ The operationsBatch function operationBatch(uint256 index) public view returns (OperationBatch memory operationBatch_) { @@ -183,7 +185,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationBatch_; } - /// @dev Return the operationsBatch with the given id + /// @notice Return the operationsBatch with the given id /// @param id The id of the operationsBatch /// @return operationBatch_ The operationsBatch function operationBatch(bytes32 id) public view returns (OperationBatch memory operationBatch_) { From 4202f5b38639c15cb39bb81c09a23cad709bd20a Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:05 -0300 Subject: [PATCH 06/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index c6954cfa..6e319045 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -/// @notice Extends the TimelockController to allow for enumerable operations +/// @dev Extends the TimelockController to allow for enumerable operations contract TimelockControllerEnumerable is TimelockController { using EnumerableSet for EnumerableSet.Bytes32Set; From 03ba004b3064f873c27e8174261c840642d679ae Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:12 -0300 Subject: [PATCH 07/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 6e319045..2a63ac1a 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -28,7 +28,7 @@ contract TimelockControllerEnumerable is TimelockController { uint256 delay; } - /// @notice The error when the operation index is not found + /// @dev The error when the operation index is not found error OperationIndexNotFound(uint256 index); /// @notice The error when the operation id is not found error OperationIdNotFound(bytes32 id); From 06db9b186c695076bab63011c217b968c4c7e792 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:19 -0300 Subject: [PATCH 08/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 2a63ac1a..ab85c760 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -30,7 +30,7 @@ contract TimelockControllerEnumerable is TimelockController { /// @dev The error when the operation index is not found error OperationIndexNotFound(uint256 index); - /// @notice The error when the operation id is not found + /// @dev The error when the operation id is not found error OperationIdNotFound(bytes32 id); /// @notice The error when the operation batch index is not found error OperationBatchIndexNotFound(uint256 index); From 1de67ee9983acfaf4e1d39d8afb8d522a69d30bc Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:26 -0300 Subject: [PATCH 09/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index ab85c760..72b784a5 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -32,7 +32,7 @@ contract TimelockControllerEnumerable is TimelockController { error OperationIndexNotFound(uint256 index); /// @dev The error when the operation id is not found error OperationIdNotFound(bytes32 id); - /// @notice The error when the operation batch index is not found + /// @dev The error when the operation batch index is not found error OperationBatchIndexNotFound(uint256 index); /// @notice The error when the operation batch id is not found error OperationBatchIdNotFound(bytes32 id); From 2c4c809b1595c99160d1b89c0cdd92273f5d3dfe Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:32 -0300 Subject: [PATCH 10/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 72b784a5..1715ff59 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -34,7 +34,7 @@ contract TimelockControllerEnumerable is TimelockController { error OperationIdNotFound(bytes32 id); /// @dev The error when the operation batch index is not found error OperationBatchIndexNotFound(uint256 index); - /// @notice The error when the operation batch id is not found + /// @dev The error when the operation batch id is not found error OperationBatchIdNotFound(bytes32 id); /// @notice The operations id set From 31a68c99450668559275cbbd1e64a810fb5b4c34 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:44 -0300 Subject: [PATCH 11/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 1715ff59..b0b075a0 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -47,7 +47,7 @@ contract TimelockControllerEnumerable is TimelockController { /// @notice The operations batch map mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; - /// @notice Initializes the contract with the given timelock parameters + /// @dev Initializes the contract with the given timelock parameters /// @param minDelay initial minimum delay in seconds for operations /// @param proposers accounts to be granted proposer and canceller roles /// @param executors accounts to be granted executor role From d82ab45ce368be7a7510b4057d5c0dd342e3b0fe Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:13:50 -0300 Subject: [PATCH 12/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index b0b075a0..056e26f1 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -156,8 +156,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @notice Return all scheduled operation batches - /// @return operationsBatch_ The operationsBatch array + /// @dev Return all scheduled operation batches function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); operationsBatch_ = new OperationBatch[](operationsBatchCount_); From 370e71f5198220005c8cd5c158e1f285d10f9123 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:14:01 -0300 Subject: [PATCH 13/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 056e26f1..f210a3a5 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -173,9 +173,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationsBatchCount_; } - /// @notice Return the operationsBatch at the given index - /// @param index The index of the operationsBatch - /// @return operationBatch_ The operationsBatch + /// @dev Return the operationsBatch at the given index function operationBatch(uint256 index) public view returns (OperationBatch memory operationBatch_) { if (index >= _operationsBatchIdSet.length()) { revert OperationBatchIndexNotFound(index); From 5e25a6286dd3c115112497231cabe405f921671f Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:14:11 -0300 Subject: [PATCH 14/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index f210a3a5..a296e963 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -182,9 +182,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationBatch_; } - /// @notice Return the operationsBatch with the given id - /// @param id The id of the operationsBatch - /// @return operationBatch_ The operationsBatch + /// @dev Return the operationsBatch with the given id function operationBatch(bytes32 id) public view returns (OperationBatch memory operationBatch_) { if (!_operationsBatchIdSet.contains(id)) { revert OperationBatchIdNotFound(id); From 4a2fa885dc4da40ba22099c949872dbc439015e9 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:14:17 -0300 Subject: [PATCH 15/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index a296e963..b0de9d7f 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -166,8 +166,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationsBatch_; } - /// @notice Return the number of operationsBatch from the set - /// @return operationsBatchCount_ The number of operationsBatch + /// @dev Return the number of operationsBatch from the set function operationsBatchCount() public view returns (uint256 operationsBatchCount_) { operationsBatchCount_ = _operationsBatchIdSet.length(); return operationsBatchCount_; From d8c165e94dcc2964d2515b916048d0a0c8e58658 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:14:38 -0300 Subject: [PATCH 16/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index b0de9d7f..591080b0 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -48,10 +48,6 @@ contract TimelockControllerEnumerable is TimelockController { mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; /// @dev Initializes the contract with the given timelock parameters - /// @param minDelay initial minimum delay in seconds for operations - /// @param proposers accounts to be granted proposer and canceller roles - /// @param executors accounts to be granted executor role - /// @param admin optional account to be granted admin role; disable with zero address constructor( uint256 minDelay, address[] memory proposers, From 79d5df3279fcf9dd783f5cbcd3e0e8dd25b4cd40 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:16:22 -0300 Subject: [PATCH 17/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 591080b0..6daca291 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -112,7 +112,7 @@ contract TimelockControllerEnumerable is TimelockController { } } - /// @notice Return all scheduled operations + /// @dev Return all scheduled operations /// @return operations_ The operations array function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); From 6bdb6a5981398c0429b7ef7dfbf3a6da1220badf Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:16:38 -0300 Subject: [PATCH 18/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 6daca291..8adafdfb 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -113,7 +113,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @dev Return all scheduled operations - /// @return operations_ The operations array function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); operations_ = new Operation[](operationsCount_); From a77ba8e39e188c0cb56eaffeb6ed610b426ef7e0 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:16:58 -0300 Subject: [PATCH 19/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 8adafdfb..e8065aef 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -113,6 +113,8 @@ contract TimelockControllerEnumerable is TimelockController { } /// @dev Return all scheduled operations + /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing + /// functions may become uncallable if the list grows too large. function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); operations_ = new Operation[](operationsCount_); From a53e607fb874ce7c44e59e403d87e9e8d94f914a Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:17:41 -0300 Subject: [PATCH 20/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index e8065aef..12c985f5 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -124,7 +124,7 @@ contract TimelockControllerEnumerable is TimelockController { return operations_; } - /// @notice Return the number of operations from the set + /// @dev Return the number of operations from the set /// @return operationsCount_ The number of operations function operationsCount() public view returns (uint256 operationsCount_) { operationsCount_ = _operationsIdSet.length(); From 9469bbb4fdbc07a203d6b727243171c4c1cccd08 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:17:58 -0300 Subject: [PATCH 21/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 12c985f5..011a12e6 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -125,7 +125,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @dev Return the number of operations from the set - /// @return operationsCount_ The number of operations function operationsCount() public view returns (uint256 operationsCount_) { operationsCount_ = _operationsIdSet.length(); return operationsCount_; From ce6e936066113237c481926d661ce878fcb5b3aa Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:18:15 -0300 Subject: [PATCH 22/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 011a12e6..01e4f655 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -130,7 +130,7 @@ contract TimelockControllerEnumerable is TimelockController { return operationsCount_; } - /// @notice Return the operation at the given index + /// @dev Return the operation at the given index /// @param index The index of the operation /// @return operation_ The operation function operation(uint256 index) public view returns (Operation memory operation_) { From 59cacbbb3693f1f6e529da4178423447d9cd1cba Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:18:31 -0300 Subject: [PATCH 23/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 01e4f655..56a9eb78 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -131,8 +131,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @dev Return the operation at the given index - /// @param index The index of the operation - /// @return operation_ The operation function operation(uint256 index) public view returns (Operation memory operation_) { if (index >= _operationsIdSet.length()) { revert OperationIndexNotFound(index); From 6fe8afeebe2adee1e726e14ca90c2dbc8b28f12e Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:18:48 -0300 Subject: [PATCH 24/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 56a9eb78..9a22d927 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -140,8 +140,6 @@ contract TimelockControllerEnumerable is TimelockController { } /// @notice Return the operation with the given id - /// @param id The id of the operation - /// @return operation_ The operation function operation(bytes32 id) public view returns (Operation memory operation_) { if (!_operationsIdSet.contains(id)) { revert OperationIdNotFound(id); From 9e373ca7d02ca4bb2fe1e08a27e6d85d15cce9f4 Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Mon, 29 Sep 2025 15:19:02 -0300 Subject: [PATCH 25/28] Update contracts/governance/TimelockControllerEnumerable.sol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/TimelockControllerEnumerable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 9a22d927..aef773a9 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -139,7 +139,7 @@ contract TimelockControllerEnumerable is TimelockController { return operation_; } - /// @notice Return the operation with the given id + /// @dev Return the operation with the given id function operation(bytes32 id) public view returns (Operation memory operation_) { if (!_operationsIdSet.contains(id)) { revert OperationIdNotFound(id); From e47ba01cff239463a0c5192c16569b54d7f643ef Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Mon, 29 Sep 2025 15:20:36 -0300 Subject: [PATCH 26/28] Add WARNING to operationsBatch --- contracts/governance/TimelockControllerEnumerable.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index aef773a9..cd13bd80 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -114,7 +114,7 @@ contract TimelockControllerEnumerable is TimelockController { /// @dev Return all scheduled operations /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing - /// functions may become uncallable if the list grows too large. + /// functions may become uncallable if the list grows too large. function operations() public view returns (Operation[] memory operations_) { uint256 operationsCount_ = _operationsIdSet.length(); operations_ = new Operation[](operationsCount_); @@ -149,6 +149,8 @@ contract TimelockControllerEnumerable is TimelockController { } /// @dev Return all scheduled operation batches + /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing + /// functions may become uncallable if the list grows too large. function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); operationsBatch_ = new OperationBatch[](operationsBatchCount_); From 6f13062b62db9140de8a7e188f4f13ffe2891c76 Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Mon, 29 Sep 2025 15:25:49 -0300 Subject: [PATCH 27/28] Make TimelockControllerEnumerable abstract --- .../TimelockControllerEnumerable.sol | 10 +----- foundry.lock | 32 +++++++++++++++++++ .../TimelockControllerEnumerable.t.sol | 5 +-- .../TimelockControllerEnumerableMock.t.sol | 17 ++++++++++ 4 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 foundry.lock create mode 100644 test/governance/TimelockControllerEnumerableMock.t.sol diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index cd13bd80..9728dbd3 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -5,7 +5,7 @@ import {TimelockController} from "@openzeppelin/contracts/governance/TimelockCon import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; /// @dev Extends the TimelockController to allow for enumerable operations -contract TimelockControllerEnumerable is TimelockController { +abstract contract TimelockControllerEnumerable is TimelockController { using EnumerableSet for EnumerableSet.Bytes32Set; /// @notice The operation struct @@ -47,14 +47,6 @@ contract TimelockControllerEnumerable is TimelockController { /// @notice The operations batch map mapping(bytes32 id => OperationBatch operationBatch) private _operationsBatchMap; - /// @dev Initializes the contract with the given timelock parameters - constructor( - uint256 minDelay, - address[] memory proposers, - address[] memory executors, - address admin - ) TimelockController(minDelay, proposers, executors, admin) {} - /// @inheritdoc TimelockController function schedule( address target, diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 00000000..86a352d9 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,32 @@ +{ + "lib/@openzeppelin-contracts": { + "branch": { + "name": "master", + "rev": "c3961a45380831135b37e55bc3ed441f678a4f5e" + } + }, + "lib/@openzeppelin-contracts-upgradeable": { + "branch": { + "name": "master", + "rev": "87e32858518950ff89c5492b6c81bb3d87cba9e3" + } + }, + "lib/axelar-gmp-sdk-solidity": { + "rev": "00682b6c3db0cc922ec0c4ea3791852c93d7ae31" + }, + "lib/email-tx-builder": { + "rev": "895e1fe943e967b0faab6a476f3b82b37d14300d" + }, + "lib/forge-std": { + "rev": "60acb7aaadcce2d68e52986a0a66fe79f07d138f" + }, + "lib/wormhole-solidity-sdk": { + "rev": "575181b586a315d8f9813eab82e4cb98b45bc381" + }, + "lib/zk-email-verify": { + "branch": { + "name": "v6.3.2", + "rev": "9ed3769dc3d96fb0d7c45f1f014dcd9bfb63675b" + } + } +} \ No newline at end of file diff --git a/test/governance/TimelockControllerEnumerable.t.sol b/test/governance/TimelockControllerEnumerable.t.sol index eb751b87..7b6793c1 100644 --- a/test/governance/TimelockControllerEnumerable.t.sol +++ b/test/governance/TimelockControllerEnumerable.t.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {TimelockControllerEnumerableMock} from "./TimelockControllerEnumerableMock.t.sol"; import { TimelockControllerEnumerable } from "@openzeppelin/community-contracts/governance/TimelockControllerEnumerable.sol"; contract TimelockControllerEnumerableTest is Test { - TimelockControllerEnumerable public timelockControllerEnumerable; + TimelockControllerEnumerableMock public timelockControllerEnumerable; event Call(); @@ -18,7 +19,7 @@ contract TimelockControllerEnumerableTest is Test { proposers[0] = address(this); executors[0] = address(this); uint256 minDelay = 1 days; - timelockControllerEnumerable = new TimelockControllerEnumerable(minDelay, proposers, executors, address(0)); + timelockControllerEnumerable = new TimelockControllerEnumerableMock(minDelay, proposers, executors, address(0)); } function call() external { diff --git a/test/governance/TimelockControllerEnumerableMock.t.sol b/test/governance/TimelockControllerEnumerableMock.t.sol new file mode 100644 index 00000000..cbe07211 --- /dev/null +++ b/test/governance/TimelockControllerEnumerableMock.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { + TimelockControllerEnumerable +} from "@openzeppelin/community-contracts/governance/TimelockControllerEnumerable.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +contract TimelockControllerEnumerableMock is TimelockControllerEnumerable { + constructor( + uint256 minDelay, + address[] memory proposers, + address[] memory executors, + address admin + ) TimelockController(minDelay, proposers, executors, admin) {} +} From ede0719ec65c736f59b85f4971df0a39f34876c3 Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Mon, 29 Sep 2025 15:31:24 -0300 Subject: [PATCH 28/28] Add range functions --- .../TimelockControllerEnumerable.sol | 41 +++++++++++++--- .../TimelockControllerEnumerable.t.sol | 47 +++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/contracts/governance/TimelockControllerEnumerable.sol b/contracts/governance/TimelockControllerEnumerable.sol index 9728dbd3..8088eccb 100644 --- a/contracts/governance/TimelockControllerEnumerable.sol +++ b/contracts/governance/TimelockControllerEnumerable.sol @@ -36,6 +36,8 @@ abstract contract TimelockControllerEnumerable is TimelockController { error OperationBatchIndexNotFound(uint256 index); /// @dev The error when the operation batch id is not found error OperationBatchIdNotFound(bytes32 id); + /// @dev The error when the index range is invalid + error InvalidIndexRange(uint256 start, uint256 end); /// @notice The operations id set EnumerableSet.Bytes32Set private _operationsIdSet; @@ -108,9 +110,21 @@ abstract contract TimelockControllerEnumerable is TimelockController { /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing /// functions may become uncallable if the list grows too large. function operations() public view returns (Operation[] memory operations_) { - uint256 operationsCount_ = _operationsIdSet.length(); - operations_ = new Operation[](operationsCount_); - for (uint256 i = 0; i < operationsCount_; i++) { + return operations(0, _operationsIdSet.length()); + } + + /// @dev Return the operations in the given index range + /// @param start The start index + /// @param end The end index + /// @return operations_ The operations + /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing + /// functions may become uncallable if the list grows too large. + function operations(uint256 start, uint256 end) public view returns (Operation[] memory operations_) { + if (start > end || start >= _operationsIdSet.length()) { + revert InvalidIndexRange(start, end); + } + operations_ = new Operation[](end - start); + for (uint256 i = start; i < end; i++) { operations_[i] = _operationsMap[_operationsIdSet.at(i)]; } return operations_; @@ -144,9 +158,24 @@ abstract contract TimelockControllerEnumerable is TimelockController { /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing /// functions may become uncallable if the list grows too large. function operationsBatch() public view returns (OperationBatch[] memory operationsBatch_) { - uint256 operationsBatchCount_ = _operationsBatchIdSet.length(); - operationsBatch_ = new OperationBatch[](operationsBatchCount_); - for (uint256 i = 0; i < operationsBatchCount_; i++) { + return operationsBatch(0, _operationsBatchIdSet.length()); + } + + /// @dev Return the operationsBatch in the given index range + /// @param start The start index + /// @param end The end index + /// @return operationsBatch_ The operationsBatch + /// WARNING: This is designed for view accessors queried without gas fees. Using it in state-changing + /// functions may become uncallable if the list grows too large. + function operationsBatch( + uint256 start, + uint256 end + ) public view returns (OperationBatch[] memory operationsBatch_) { + if (start > end || start >= _operationsBatchIdSet.length()) { + revert InvalidIndexRange(start, end); + } + operationsBatch_ = new OperationBatch[](end - start); + for (uint256 i = start; i < end; i++) { operationsBatch_[i] = _operationsBatchMap[_operationsBatchIdSet.at(i)]; } return operationsBatch_; diff --git a/test/governance/TimelockControllerEnumerable.t.sol b/test/governance/TimelockControllerEnumerable.t.sol index 7b6793c1..182e2b40 100644 --- a/test/governance/TimelockControllerEnumerable.t.sol +++ b/test/governance/TimelockControllerEnumerable.t.sol @@ -59,6 +59,29 @@ contract TimelockControllerEnumerableTest is Test { assertEq(operation.delay, 1 days); } + function test_operations() public { + test_schedule(); + TimelockControllerEnumerable.Operation[] memory operations = timelockControllerEnumerable.operations(0, 1); + assertEq(operations.length, 1); + assertEq(operations[0].target, address(this)); + assertEq(operations[0].value, 0); + assertEq(operations[0].data, abi.encodeCall(this.call, ())); + assertEq(operations[0].predecessor, bytes32(0)); + assertEq(operations[0].salt, bytes32(0)); + assertEq(operations[0].delay, 1 days); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.InvalidIndexRange.selector, 2, 1)); + timelockControllerEnumerable.operations(2, 1); + + operations = timelockControllerEnumerable.operations(); + assertEq(operations.length, 1); + assertEq(operations[0].target, address(this)); + assertEq(operations[0].value, 0); + assertEq(operations[0].data, abi.encodeCall(this.call, ())); + assertEq(operations[0].predecessor, bytes32(0)); + assertEq(operations[0].salt, bytes32(0)); + assertEq(operations[0].delay, 1 days); + } + function test_schedule_execute() public { test_schedule(); TimelockControllerEnumerable.Operation memory operation = timelockControllerEnumerable.operation(uint256(0)); @@ -109,6 +132,30 @@ contract TimelockControllerEnumerableTest is Test { assertEq(operationBatch.delay, 1 days); } + function test_operationsBatch() public { + test_scheduleBatch(); + TimelockControllerEnumerable.OperationBatch[] memory operationBatches = timelockControllerEnumerable + .operationsBatch(0, 1); + assertEq(operationBatches.length, 1); + assertEq(operationBatches[0].targets[0], address(this)); + assertEq(operationBatches[0].values[0], 0); + assertEq(operationBatches[0].payloads[0], abi.encodeCall(this.call, ())); + assertEq(operationBatches[0].predecessor, bytes32(0)); + assertEq(operationBatches[0].salt, bytes32(0)); + assertEq(operationBatches[0].delay, 1 days); + vm.expectRevert(abi.encodeWithSelector(TimelockControllerEnumerable.InvalidIndexRange.selector, 2, 1)); + timelockControllerEnumerable.operationsBatch(2, 1); + + operationBatches = timelockControllerEnumerable.operationsBatch(); + assertEq(operationBatches.length, 1); + assertEq(operationBatches[0].targets[0], address(this)); + assertEq(operationBatches[0].values[0], 0); + assertEq(operationBatches[0].payloads[0], abi.encodeCall(this.call, ())); + assertEq(operationBatches[0].predecessor, bytes32(0)); + assertEq(operationBatches[0].salt, bytes32(0)); + assertEq(operationBatches[0].delay, 1 days); + } + function test_scheduleBatch_execute() public { test_scheduleBatch(); TimelockControllerEnumerable.OperationBatch memory operationBatch = timelockControllerEnumerable.operationBatch(