tell me more about how the scheduling and execution of operations work, including any dependencies between operations? // SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

// ███████╗███████╗██████╗ ██████╗
// ╚══███╔╝██╔════╝██╔══██╗██╔═══██╗
// ███╔╝ █████╗ ██████╔╝██║ ██║
// ███╔╝ ██╔══╝ ██╔══██╗██║ ██║
// ███████╗███████╗██║ ██║╚██████╔╝
// ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝

// Website: https://zerolend.xyz
// Discord: https://discord.gg/zerolend
// Twitter: https://twitter.com/zerolendxyz

import “@openzeppelin/contracts/access/AccessControlEnumerable.sol”;
import “@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol”;
import “@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol”;
import {Address} from “@openzeppelin/contracts/utils/Address.sol”;

/
* @dev Contract module which acts as a timelocked controller. When set as the
* owner of an Ownable smart contract, it enforces a timelock on all
* onlyOwner maintenance operations. This gives time for users of the
* controlled contract to exit before a potentially dangerous maintenance
* operation is applied.
*
* By default, this contract is self administered, meaning administration tasks
* have to go through the timelock process. The proposer (resp executor) role
* is in charge of proposing (resp executing) operations. A common use case is
* to position this {TimelockController} as the owner of a smart contract, with
* a multisig or a DAO as the sole proposer.
*
* Available since v3.3.
*/

contract ZeroLockerTimelock is
AccessControlEnumerable,
ERC721Holder,
ERC1155Holder
{
bytes32 public constant TIMELOCK_ADMIN_ROLE =
keccak256(“TIMELOCK_ADMIN_ROLE”);
bytes32 public constant PROPOSER_ROLE = keccak256(“PROPOSER_ROLE”);
bytes32 public constant EXECUTOR_ROLE = keccak256(“EXECUTOR_ROLE”);
bytes32 public constant CANCELLER_ROLE = keccak256(“CANCELLER_ROLE”);
uint256 internal constant _DONE_TIMESTAMP = uint256(1);

mapping(bytes32 => uint256) private _timestamps;
uint256 private _minDelay;

/
* @dev Emitted when a call is scheduled as part of operation id.
*/
event CallScheduled(
bytes32 indexed id,
uint256 indexed index,
address target,
uint256 value,
bytes data,
bytes32 predecessor,
uint256 delay
);

/
* @dev Emitted when a call is performed as part of operation id.
*/
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.
*/
event Cancelled(bytes32 indexed id);

/
* @dev Emitted when the minimum delay for future operations is modified.
*/
event MinDelayChange(uint256 oldDuration, uint256 newDuration);

/
* @dev Initializes the contract with the following parameters:
*
* - minDelay: initial minimum delay for operations
* - proposers: accounts to be granted proposer and canceller roles
* - executors: accounts to be granted executor role
* - admin: optional account to be granted admin role; disable with zero address
*
* IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
* without being subject to delay, but this role should be subsequently renounced in favor of
* administration through timelocked proposals. Previous versions of this contract would assign
* this admin to the deployer automatically and should be renounced as well.
*/
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors,
address admin
) {
_setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
_setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);

// self administration
_setupRole(TIMELOCK_ADMIN_ROLE, address(this));

// optional admin
if (admin != address(0)) {
_setupRole(TIMELOCK_ADMIN_ROLE, admin);
}

// register proposers and cancellers
for (uint256 i = 0; i < proposers.length; ++i) {
_setupRole(PROPOSER_ROLE, proposers[i]);
_setupRole(CANCELLER_ROLE, proposers[i]);
}

// register executors
for (uint256 i = 0; i < executors.length; ++i) {
_setupRole(EXECUTOR_ROLE, executors[i]);
}

_minDelay = minDelay;
emit MinDelayChange(0, minDelay);
}

/
* @dev Modifier to make a function callable only by a certain role. In
* addition to checking the sender’s role, address(0) 's role is also
* considered. Granting a role to address(0) is equivalent to enabling
* this role for everyone.
*/
modifier onlyRoleOrOpenRole(bytes32 role) {
if (!hasRole(role, address(0))) {
_checkRole(role, _msgSender());
}
_;
}

/
* @dev Contract might receive/hold ETH as part of the maintenance process.
*/
receive() external payable {}

/
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
)
public
view
virtual
override(ERC1155Receiver, AccessControlEnumerable)
returns (bool)
{
return
interfaceId == type(IERC1155Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}

/
* @dev Returns whether an id correspond to a registered operation. This
* includes both Pending, Ready and Done operations.
*/
function isOperation(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > 0;
}

/
* @dev Returns whether an operation is pending or not. Note that a “pending” operation may also be “ready”.
*/
function isOperationPending(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) > _DONE_TIMESTAMP;
}

/
* @dev Returns whether an operation is ready for execution. Note that a “ready” operation is also “pending”.
*/
function isOperationReady(bytes32 id) public view virtual returns (bool) {
uint256 timestamp = getTimestamp(id);
return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
}

/
* @dev Returns whether an operation is done or not.
*/
function isOperationDone(bytes32 id) public view virtual returns (bool) {
return getTimestamp(id) == _DONE_TIMESTAMP;
}

/
* @dev Returns the timestamp at which an operation becomes ready (0 for
* unset operations, 1 for done operations).
*/
function getTimestamp(bytes32 id) public view virtual returns (uint256) {
return _timestamps[id];
}

/
* @dev Returns the minimum delay for an operation to become valid.
*
* This value can be changed by executing an operation that calls updateDelay.
*/
function getMinDelay() public view virtual returns (uint256) {
return _minDelay;
}

/
* @dev Returns the identifier of an operation containing a single
* transaction.
*/
function hashOperation(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(target, value, data, predecessor, salt));
}

/
* @dev Returns the identifier of an operation containing a batch of
* transactions.
*/
function hashOperationBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return
keccak256(abi.encode(targets, values, payloads, predecessor, salt));
}

/
* @dev Schedule an operation containing a single transaction.
*
* Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
*
* Requirements:
*
* - the caller must have the ‘proposer’ role.
*/
function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
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 {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the ‘proposer’ role.
*/
function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual onlyRole(PROPOSER_ROLE) {
require(
targets.length == values.length,
“TimelockController: length mismatch”
);
require(
targets.length == payloads.length,
“TimelockController: length mismatch”
);

bytes32 id = hashOperationBatch(
targets,
values,
payloads,
predecessor,
salt
);
_schedule(id, delay);
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);
}
}

/
* @dev Schedule an operation that is to become valid after a given delay.
*/
function _schedule(bytes32 id, uint256 delay) private {
require(
!isOperation(id),
“TimelockController: operation already scheduled”
);
require(
delay >= getMinDelay(),
“TimelockController: insufficient delay”
);
_timestamps[id] = block.timestamp + delay;
}

/
* @dev Cancel an operation.
*
* Requirements:
*
* - the caller must have the ‘canceller’ role.
*/
function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
require(
isOperationPending(id),
“TimelockController: operation cannot be cancelled”
);
delete _timestamps[id];

emit Cancelled(id);
}

/
* @dev Execute an (ready) operation containing a single transaction.
*
* Emits a {CallExecuted} event.
*
* Requirements:
*
* - the caller must have the ‘executor’ role.
*/
// This function can reenter, but it doesn’t pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function execute(
address target,
uint256 value,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
bytes32 id = hashOperation(target, value, payload, predecessor, salt);

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

/
* @dev Execute an (ready) operation containing a batch of transactions.
*
* Emits one {CallExecuted} event per transaction in the batch.
*
* Requirements:
*
* - the caller must have the ‘executor’ role.
*/
// This function can reenter, but it doesn’t pose a risk because _afterCall checks that the proposal is pending,
// thus any modifications to the operation during reentrancy should be caught.
// slither-disable-next-line reentrancy-eth
function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
require(
targets.length == values.length,
“TimelockController: length mismatch”
);
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) {
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, ) = target.call{value: value}(data);
require(success, “TimelockController: underlying transaction reverted”);
}

/
* @dev Checks before execution of an operation’s calls.
*/
function _beforeCall(bytes32 id, bytes32 predecessor) private view {
require(
isOperationReady(id),
“TimelockController: operation is not ready”
);
require(
predecessor == bytes32(0) || isOperationDone(predecessor),
“TimelockController: missing dependency”
);
}

/
* @dev Checks after execution of an operation’s calls.
*/
function _afterCall(bytes32 id) private {
require(
isOperationReady(id),
“TimelockController: operation is not ready”
);
_timestamps[id] = _DONE_TIMESTAMP;
}

/
* @dev Changes the minimum timelock duration for future operations.
*
* Emits a {MinDelayChange} event.
*
* Requirements:
*
* - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
* an operation where the timelock is the target and the data is the ABI-encoded call to this function.
*/
function updateDelay(uint256 newDelay) external virtual {
require(
msg.sender == address(this),
“TimelockController: caller must be timelock”
);
emit MinDelayChange(_minDelay, newDelay);
_minDelay = newDelay;
}

/
* @dev See {IERC721Receiver-onERC721Received}.
*/
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}

/
* @dev See {IERC1155Receiver-onERC1155Received}.
*/
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}

/**
* @dev See {IERC1155Receiver-onERC1155BatchReceived}.
*/
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}