Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"lib/network/src/Network.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {INetwork} from "./interfaces/INetwork.sol";
import {ISetMaxNetworkLimitHook} from "./interfaces/ISetMaxNetworkLimitHook.sol";
import {IBaseDelegator} from "@symbioticfi/core/src/interfaces/delegator/IBaseDelegator.sol";
import {INetworkMiddlewareService} from "@symbioticfi/core/src/interfaces/service/INetworkMiddlewareService.sol";
import {INetworkRegistry} from "@symbioticfi/core/src/interfaces/INetworkRegistry.sol";
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
import {TimelockControllerUpgradeable} from
"@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
/**
* @title Network
* @notice Contract for network management.
* @dev It allows any external watcher to verify if the set delay is sufficient for a given operation (call to some contract's function).
* It supports delays for native asset transfers (native transfer is determined as a call with 0xEEEEEEEE selector).
* It supports setting delay for (exact target | exact selector) pairs and for (any target | exact selector) pairs.
*/
contract Network is TimelockControllerUpgradeable, INetwork {
using Bytes for bytes;
/**
* @inheritdoc INetwork
*/
bytes32 public constant NAME_UPDATE_ROLE = keccak256("NAME_UPDATE_ROLE");
/**
* @inheritdoc INetwork
*/
bytes32 public constant METADATA_URI_UPDATE_ROLE = keccak256("METADATA_URI_UPDATE_ROLE");
/**
* @inheritdoc INetwork
*/
address public immutable NETWORK_REGISTRY;
/**
* @inheritdoc INetwork
*/
address public immutable NETWORK_MIDDLEWARE_SERVICE;
// keccak256(abi.encode(uint256(keccak256("symbiotic.storage.Network")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant NetworkStorageLocation = 0x2affd7691de6b6d2a998e6b135d73a3c906ea64896dff9dcb273e98dd44a6100;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.TimelockController")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant TimelockControllerStorageLocation =
0x9a37c2aa9d186a0969ff8a8267bf4e07e864c2f2768f5040949e28a624fb3600;
constructor(address networkRegistry, address networkMiddlewareService) {
NETWORK_REGISTRY = networkRegistry;
NETWORK_MIDDLEWARE_SERVICE = networkMiddlewareService;
}
function _getNetworkStorage() internal pure returns (NetworkStorage storage $) {
bytes32 location = NetworkStorageLocation;
assembly {
$.slot := location
}
}
function _getTimelockControllerStorageOverridden() internal pure returns (TimelockControllerStorage storage $) {
assembly {
$.slot := TimelockControllerStorageLocation
}
}
/**
* @inheritdoc INetwork
*/
function initialize(
NetworkInitParams memory initParams
) public virtual initializer {
__Network_init(initParams);
}
function __Network_init(
NetworkInitParams memory initParams
) internal virtual onlyInitializing {
__TimelockController_init(
initParams.globalMinDelay, initParams.proposers, initParams.executors, initParams.defaultAdminRoleHolder
);
INetworkRegistry(NETWORK_REGISTRY).registerNetwork();
_updateName(initParams.name);
_updateMetadataURI(initParams.metadataURI);
for (uint256 i; i < initParams.delayParams.length; ++i) {
_updateDelay(
initParams.delayParams[i].target,
initParams.delayParams[i].selector,
true,
initParams.delayParams[i].delay
);
}
if (initParams.defaultAdminRoleHolder != address(0)) {
_grantRole(DEFAULT_ADMIN_ROLE, initParams.defaultAdminRoleHolder);
}
if (initParams.nameUpdateRoleHolder != address(0)) {
_grantRole(NAME_UPDATE_ROLE, initParams.nameUpdateRoleHolder);
}
if (initParams.metadataURIUpdateRoleHolder != address(0)) {
_grantRole(METADATA_URI_UPDATE_ROLE, initParams.metadataURIUpdateRoleHolder);
}
}
/**
* @inheritdoc INetwork
*/
function getMinDelay(address target, bytes memory data) public view virtual returns (uint256) {
bytes4 selector = _getSelector(data);
if (target == address(this)) {
if (selector == INetwork.updateDelay.selector) {
(address underlyingTarget, bytes4 underlyingSelector,,) =
abi.decode(_getPayload(data), (address, bytes4, bool, uint256));
if (
underlyingTarget == address(this)
&& (
underlyingSelector == INetwork.updateDelay.selector
|| underlyingSelector == TimelockControllerUpgradeable.updateDelay.selector
)
) {
revert InvalidTargetAndSelector();
}
return _getMinDelay(underlyingTarget, underlyingSelector);
}
if (selector == TimelockControllerUpgradeable.updateDelay.selector) {
return getMinDelay();
}
}
if (target == address(0)) {
revert InvalidTargetAndSelector();
}
return _getMinDelay(target, selector);
}
/**
* @inheritdoc INetwork
*/
function name() public view virtual returns (string memory) {
return _getNetworkStorage()._name;
}
/**
* @inheritdoc INetwork
*/
function metadataURI() public view virtual returns (string memory) {
return _getNetworkStorage()._metadataURI;
}
/**
* @inheritdoc INetwork
*/
function updateDelay(address target, bytes4 selector, bool enabled, uint256 newDelay) public virtual {
address sender = _msgSender();
if (sender != address(this)) {
revert TimelockUnauthorizedCaller(sender);
}
_updateDelay(target, selector, enabled, newDelay);
}
/**
* @inheritdoc TimelockControllerUpgradeable
*/
function schedule(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual override onlyRole(PROPOSER_ROLE) {
uint256 minDelay = getMinDelay(target, data);
if (delay < minDelay) {
revert TimelockInsufficientDelay(delay, minDelay);
}
bytes32 id = hashOperation(target, value, data, predecessor, salt);
_scheduleOverridden(id, delay);
emit CallScheduled(id, 0, target, value, data, predecessor, delay);
if (salt != bytes32(0)) {
emit CallSalt(id, salt);
}
}
/**
* @inheritdoc TimelockControllerUpgradeable
*/
function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) public virtual override onlyRole(PROPOSER_ROLE) {
if (targets.length != values.length || targets.length != payloads.length) {
revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length);
}
for (uint256 i; i < targets.length; ++i) {
uint256 minDelay = getMinDelay(targets[i], payloads[i]);
if (delay < minDelay) {
revert TimelockInsufficientDelay(delay, minDelay);
}
}
bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt);
_scheduleOverridden(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);
}
}
/**
* @inheritdoc INetwork
*/
function updateName(
string memory name_
) public virtual onlyRole(NAME_UPDATE_ROLE) {
_updateName(name_);
}
/**
* @inheritdoc INetwork
*/
function updateMetadataURI(
string memory metadataURI_
) public virtual onlyRole(METADATA_URI_UPDATE_ROLE) {
_updateMetadataURI(metadataURI_);
}
/**
* @inheritdoc ISetMaxNetworkLimitHook
*/
function setMaxNetworkLimit(address delegator, uint96 subnetworkId, uint256 maxNetworkLimit) public virtual {
if (INetworkMiddlewareService(NETWORK_MIDDLEWARE_SERVICE).middleware(address(this)) != msg.sender) {
revert NotMiddleware();
}
_setMaxNetworkLimit(delegator, subnetworkId, maxNetworkLimit);
}
function _updateDelay(address target, bytes4 selector, bool enabled, uint256 newDelay) internal virtual {
NetworkStorage storage $ = _getNetworkStorage();
if (!enabled && newDelay != 0) {
revert InvalidNewDelay();
}
bytes32 id = _getId(target, selector);
emit MinDelayChange(target, selector, $._isMinDelayEnabled[id], $._minDelays[id], enabled, newDelay);
$._isMinDelayEnabled[id] = enabled;
$._minDelays[id] = newDelay;
}
function _scheduleOverridden(bytes32 id, uint256 delay) internal virtual {
TimelockControllerStorage storage $ = _getTimelockControllerStorageOverridden();
if (isOperation(id)) {
revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset));
}
$._timestamps[id] = block.timestamp + delay;
}
function _updateName(
string memory name_
) internal virtual {
_getNetworkStorage()._name = name_;
emit NameSet(name_);
}
function _updateMetadataURI(
string memory metadataURI_
) internal virtual {
_getNetworkStorage()._metadataURI = metadataURI_;
emit MetadataURISet(metadataURI_);
}
function _setMaxNetworkLimit(address delegator, uint96 subnetworkId, uint256 maxNetworkLimit) internal virtual {
IBaseDelegator(delegator).setMaxNetworkLimit(subnetworkId, maxNetworkLimit);
}
function _getId(address target, bytes4 selector) internal pure virtual returns (bytes32 id) {
return keccak256(abi.encode(target, selector));
}
function _getMinDelay(address target, bytes4 selector) internal view virtual returns (uint256) {
(bool enabled, uint256 minDelay) = _getMinDelay(_getId(target, selector));
if (enabled) {
return minDelay;
}
(enabled, minDelay) = _getMinDelay(_getId(address(0), selector));
if (enabled) {
return minDelay;
}
return getMinDelay();
}
function _getMinDelay(
bytes32 id
) internal view virtual returns (bool, uint256) {
NetworkStorage storage $ = _getNetworkStorage();
return ($._isMinDelayEnabled[id], $._minDelays[id]);
}
function _getSelector(
bytes memory data
) internal pure returns (bytes4 selector) {
if (data.length == 0) {
return 0xEEEEEEEE;
}
if (data.length < 4) {
revert InvalidDataLength();
}
assembly ("memory-safe") {
selector := mload(add(data, 32))
}
}
function _getPayload(
bytes memory data
) internal pure returns (bytes memory payload) {
if (data.length < 4) {
revert InvalidDataLength();
}
return data.slice(4);
}
/**
* @inheritdoc TimelockControllerUpgradeable
*/
function initialize(
uint256, /* minDelay */
address[] memory, /* proposers */
address[] memory, /* executors */
address /* admin */
) public virtual override {
revert();
}
}
"
},
"lib/network/src/interfaces/INetwork.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISetMaxNetworkLimitHook} from "./ISetMaxNetworkLimitHook.sol";
interface INetwork is ISetMaxNetworkLimitHook {
/**
* @notice Reverts when the calldata length is invalid.
*/
error InvalidDataLength();
/**
* @notice Reverts when the new delay is non-zero but disabled.
*/
error InvalidNewDelay();
/**
* @notice Reverts when the "recursive" delay update is attempted.
*/
error InvalidTargetAndSelector();
/**
* @notice Reverts when the caller is not the network's middleware.
*/
error NotMiddleware();
/**
* @notice The storage of the Network contract.
* @param _minDelays The mapping from the id (derived from the target and the selector) to the minimum delay.
* @param _isMinDelayEnabled The mapping from the id (derived from the target and the selector) to the minimum delay enabled status.
* @param _name The name of the network.
* @param _metadataURI The metadata URI of the network.
* @custom:storage-location erc7201:symbiotic.storage.Network
*/
struct NetworkStorage {
mapping(bytes32 id => uint256 minDelay) _minDelays;
mapping(bytes32 => bool) _isMinDelayEnabled;
string _name;
string _metadataURI;
}
/**
* @notice The parameters for the initialization of the Network contract.
* @param globalMinDelay The global minimum delay.
* @param delayParams The delays.
* @param proposers The proposers.
* @param executors The executors.
* @param name The name of the network.
* @param metadataURI The metadata URI of the network.
* @param defaultAdminRoleHolder The address of the default admin role holder.
* @param nameUpdateRoleHolder The address of the name update role holder.
* @param metadataURIUpdateRoleHolder The address of the metadata URI update role holder.
*/
struct NetworkInitParams {
uint256 globalMinDelay;
DelayParams[] delayParams;
address[] proposers;
address[] executors;
string name;
string metadataURI;
address defaultAdminRoleHolder;
address nameUpdateRoleHolder;
address metadataURIUpdateRoleHolder;
}
/**
* @notice The delay parameters.
* @param target The target address the delay is for.
* @param selector The function selector the delay is for.
* @param delay The delay value.
*/
struct DelayParams {
address target;
bytes4 selector;
uint256 delay;
}
/**
* @notice Emitted when the minimum delay is changed.
* @param target The target address the delay is for.
* @param selector The function selector the delay is for.
* @param oldEnabledStatus The old enabled status.
* @param oldDelay The old delay value.
* @param newEnabledStatus The new enabled status.
* @param newDelay The new delay value.
*/
event MinDelayChange(
address indexed target,
bytes4 indexed selector,
bool oldEnabledStatus,
uint256 oldDelay,
bool newEnabledStatus,
uint256 newDelay
);
/**
* @notice Emitted when the name is set.
* @param name The name of the network.
*/
event NameSet(string name);
/**
* @notice Emitted when the metadata URI is set.
* @param metadataURI The metadata URI of the network.
*/
event MetadataURISet(string metadataURI);
/**
* @notice Returns the role for updating the name.
* @return The role for updating the name.
*/
function NAME_UPDATE_ROLE() external view returns (bytes32);
/**
* @notice Returns the role for updating the metadata URI.
* @return The role for updating the metadata URI.
*/
function METADATA_URI_UPDATE_ROLE() external view returns (bytes32);
/**
* @notice Returns the address of the network registry.
* @return The address of the network registry.
*/
function NETWORK_REGISTRY() external view returns (address);
/**
* @notice Returns the address of the network middleware service.
* @return The address of the network middleware service.
*/
function NETWORK_MIDDLEWARE_SERVICE() external view returns (address);
/**
* @notice Returns the minimum delay for a given target and calldata.
* @param target The target address the delay is for.
* @param data The calldata of the function call.
* @return The minimum delay for a given target and calldata.
*/
function getMinDelay(address target, bytes memory data) external view returns (uint256);
/**
* @notice Returns the name of the network.
* @return The name of the network.
*/
function name() external view returns (string memory);
/**
* @notice Returns the metadata URI of the network.
* @return The metadata URI of the network.
*/
function metadataURI() external view returns (string memory);
/**
* @notice Initializes the network.
* @param networkInitParams The parameters for the initialization of the network.
*/
function initialize(
NetworkInitParams memory networkInitParams
) external;
/**
* @notice Updates the delay for a given target and selector.
* @param target The target address the delay is for.
* @param selector The function selector the delay is for.
* @param enabled If to enable the delay.
* @param newDelay The new delay value.
* @dev Can be reached only via scheduled calls.
*/
function updateDelay(address target, bytes4 selector, bool enabled, uint256 newDelay) external;
/**
* @notice Updates the name of the network.
* @param name The new name.
* @dev The caller must have the name update role.
*/
function updateName(
string memory name
) external;
/**
* @notice Updates the metadata URI of the network.
* @param metadataURI The new metadata URI.
* @dev The caller must have the metadata URI update role.
*/
function updateMetadataURI(
string memory metadataURI
) external;
}
"
},
"lib/network/src/interfaces/ISetMaxNetworkLimitHook.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ISetMaxNetworkLimitHook {
/**
* @notice Sets the maximum network limit for a delegator.
* @param delegator The address of the delegator.
* @param subnetworkId The identifier of the subnetwork.
* @param maxNetworkLimit The maximum network limit.
* @dev The caller must be the network's middleware.
*/
function setMaxNetworkLimit(address delegator, uint96 subnetworkId, uint256 maxNetworkLimit) external;
}
"
},
"lib/core/src/interfaces/delegator/IBaseDelegator.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEntity} from "../common/IEntity.sol";
interface IBaseDelegator is IEntity {
error AlreadySet();
error InsufficientHookGas();
error NotNetwork();
error NotSlasher();
error NotVault();
/**
* @notice Base parameters needed for delegators' deployment.
* @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder
* @param hook address of the hook contract
* @param hookSetRoleHolder address of the initial HOOK_SET_ROLE holder
*/
struct BaseParams {
address defaultAdminRoleHolder;
address hook;
address hookSetRoleHolder;
}
/**
* @notice Base hints for a stake.
* @param operatorVaultOptInHint hint for the operator-vault opt-in
* @param operatorNetworkOptInHint hint for the operator-network opt-in
*/
struct StakeBaseHints {
bytes operatorVaultOptInHint;
bytes operatorNetworkOptInHint;
}
/**
* @notice Emitted when a subnetwork's maximum limit is set.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param amount new maximum subnetwork's limit (how much stake the subnetwork is ready to get)
*/
event SetMaxNetworkLimit(bytes32 indexed subnetwork, uint256 amount);
/**
* @notice Emitted when a slash happens.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param amount amount of the collateral to be slashed
* @param captureTimestamp time point when the stake was captured
*/
event OnSlash(bytes32 indexed subnetwork, address indexed operator, uint256 amount, uint48 captureTimestamp);
/**
* @notice Emitted when a hook is set.
* @param hook address of the hook
*/
event SetHook(address indexed hook);
/**
* @notice Get a version of the delegator (different versions mean different interfaces).
* @return version of the delegator
* @dev Must return 1 for this one.
*/
function VERSION() external view returns (uint64);
/**
* @notice Get the network registry's address.
* @return address of the network registry
*/
function NETWORK_REGISTRY() external view returns (address);
/**
* @notice Get the vault factory's address.
* @return address of the vault factory
*/
function VAULT_FACTORY() external view returns (address);
/**
* @notice Get the operator-vault opt-in service's address.
* @return address of the operator-vault opt-in service
*/
function OPERATOR_VAULT_OPT_IN_SERVICE() external view returns (address);
/**
* @notice Get the operator-network opt-in service's address.
* @return address of the operator-network opt-in service
*/
function OPERATOR_NETWORK_OPT_IN_SERVICE() external view returns (address);
/**
* @notice Get a gas limit for the hook.
* @return value of the hook gas limit
*/
function HOOK_GAS_LIMIT() external view returns (uint256);
/**
* @notice Get a reserve gas between the gas limit check and the hook's execution.
* @return value of the reserve gas
*/
function HOOK_RESERVE() external view returns (uint256);
/**
* @notice Get a hook setter's role.
* @return identifier of the hook setter role
*/
function HOOK_SET_ROLE() external view returns (bytes32);
/**
* @notice Get the vault's address.
* @return address of the vault
*/
function vault() external view returns (address);
/**
* @notice Get the hook's address.
* @return address of the hook
* @dev The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
*/
function hook() external view returns (address);
/**
* @notice Get a particular subnetwork's maximum limit
* (meaning the subnetwork is not ready to get more as a stake).
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @return maximum limit of the subnetwork
*/
function maxNetworkLimit(
bytes32 subnetwork
) external view returns (uint256);
/**
* @notice Get a stake that a given subnetwork could be able to slash for a certain operator at a given timestamp
* until the end of the consequent epoch using hints (if no cross-slashing and no slashings by the subnetwork).
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param timestamp time point to capture the stake at
* @param hints hints for the checkpoints' indexes
* @return slashable stake at the given timestamp until the end of the consequent epoch
* @dev Warning: it is not safe to use timestamp >= current one for the stake capturing, as it can change later.
*/
function stakeAt(
bytes32 subnetwork,
address operator,
uint48 timestamp,
bytes memory hints
) external view returns (uint256);
/**
* @notice Get a stake that a given subnetwork will be able to slash
* for a certain operator until the end of the next epoch (if no cross-slashing and no slashings by the subnetwork).
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @return slashable stake until the end of the next epoch
* @dev Warning: this function is not safe to use for stake capturing, as it can change by the end of the block.
*/
function stake(bytes32 subnetwork, address operator) external view returns (uint256);
/**
* @notice Set a maximum limit for a subnetwork (how much stake the subnetwork is ready to get).
* identifier identifier of the subnetwork
* @param amount new maximum subnetwork's limit
* @dev Only a network can call this function.
*/
function setMaxNetworkLimit(uint96 identifier, uint256 amount) external;
/**
* @notice Set a new hook.
* @param hook address of the hook
* @dev Only a HOOK_SET_ROLE holder can call this function.
* The hook can have arbitrary logic under certain functions, however, it doesn't affect the stake guarantees.
*/
function setHook(
address hook
) external;
/**
* @notice Called when a slash happens.
* @param subnetwork full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
* @param operator address of the operator
* @param amount amount of the collateral slashed
* @param captureTimestamp time point when the stake was captured
* @param data some additional data
* @dev Only the vault's slasher can call this function.
*/
function onSlash(
bytes32 subnetwork,
address operator,
uint256 amount,
uint48 captureTimestamp,
bytes calldata data
) external;
}
"
},
"lib/core/src/interfaces/service/INetworkMiddlewareService.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface INetworkMiddlewareService {
error AlreadySet();
error NotNetwork();
/**
* @notice Emitted when a middleware is set for a network.
* @param network address of the network
* @param middleware new middleware of the network
*/
event SetMiddleware(address indexed network, address middleware);
/**
* @notice Get the network registry's address.
* @return address of the network registry
*/
function NETWORK_REGISTRY() external view returns (address);
/**
* @notice Get a given network's middleware.
* @param network address of the network
* @return middleware of the network
*/
function middleware(
address network
) external view returns (address);
/**
* @notice Set a new middleware for a calling network.
* @param middleware new middleware of the network
*/
function setMiddleware(
address middleware
) external;
}
"
},
"lib/core/src/interfaces/INetworkRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IRegistry} from "./common/IRegistry.sol";
interface INetworkRegistry is IRegistry {
error NetworkAlreadyRegistered();
/**
* @notice Register the caller as a network.
*/
function registerNetwork() external;
}
"
},
"lib/network/lib/openzeppelin-contracts/contracts/utils/Bytes.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Bytes.sol)
pragma solidity ^0.8.24;
import {Math} from "./math/Math.sol";
/**
* @dev Bytes operations.
*/
library Bytes {
/**
* @dev Forward search for `s` in `buffer`
* * If `s` is present in the buffer, returns the index of the first instance
* * If `s` is not present in the buffer, returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
*/
function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
return indexOf(buffer, s, 0);
}
/**
* @dev Forward search for `s` in `buffer` starting at position `pos`
* * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance
* * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
*/
function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
uint256 length = buffer.length;
for (uint256 i = pos; i < length; ++i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
return i;
}
}
return type(uint256).max;
}
/**
* @dev Backward search for `s` in `buffer`
* * If `s` is present in the buffer, returns the index of the last instance
* * If `s` is not present in the buffer, returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
*/
function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
return lastIndexOf(buffer, s, type(uint256).max);
}
/**
* @dev Backward search for `s` in `buffer` starting at position `pos`
* * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance
* * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
*/
function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
unchecked {
uint256 length = buffer.length;
for (uint256 i = Math.min(Math.saturatingAdd(pos, 1), length); i > 0; --i) {
if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) {
return i - 1;
}
}
return type(uint256).max;
}
}
/**
* @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) {
return slice(buffer, start, buffer.length);
}
/**
* @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in
* memory.
*
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
*/
function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) {
// sanitize
uint256 length = buffer.length;
end = Math.min(end, length);
start = Math.min(start, end);
// allocate and copy
bytes memory result = new bytes(end - start);
assembly ("memory-safe") {
mcopy(add(result, 0x20), add(add(buffer, 0x20), start), sub(end, start))
}
return result;
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(add(buffer, 0x20), offset))
}
}
}
"
},
"lib/network/lib/openzeppelin-contracts-upgradeable/contracts/governance/TimelockControllerUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (governance/TimelockController.sol)
pragma solidity ^0.8.20;
import {AccessControlUpgradeable} from "../access/AccessControlUpgradeable.sol";
import {ERC721HolderUpgradeable} from "../token/ERC721/utils/ERC721HolderUpgradeable.sol";
import {ERC1155HolderUpgradeable} from "../token/ERC1155/utils/ERC1155HolderUpgradeable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Initializable} from "../proxy/utils/Initializable.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.
*/
contract TimelockControllerUpgradeable is Initializable, AccessControlUpgradeable, ERC721HolderUpgradeable, ERC1155HolderUpgradeable {
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);
/// @custom:storage-location erc7201:openzeppelin.storage.TimelockController
struct TimelockControllerStorage {
mapping(bytes32 id => uint256) _timestamps;
uint256 _minDelay;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.TimelockController")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant TimelockControllerStorageLocation = 0x9a37c2aa9d186a0969ff8a8267bf4e07e864c2f2768f5040949e28a624fb3600;
function _getTimelockControllerStorage() private pure returns (TimelockControllerStorage storage $) {
assembly {
$.slot := TimelockControllerStorageLocation
}
}
enum OperationState {
Unset,
Waiting,
Ready,
Done
}
/**
* @dev Mismatch between the parameters length for an operation call.
*/
error TimelockInvalidOperationLength(uint256 targets, uint256 payloads, uint256 values);
/**
* @dev The schedule operation doesn't meet the minimum delay.
*/
error TimelockInsufficientDelay(uint256 delay, uint256 minDelay);
/**
* @dev The current state of an operation is not as required.
* The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position
* counting from right to left.
*
* See {_encodeStateBitmap}.
*/
error TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates);
/**
* @dev The predecessor to an operation not yet done.
*/
error TimelockUnexecutedPredecessor(bytes32 predecessorId);
/**
* @dev The caller account is not authorized.
*/
error TimelockUnauthorizedCaller(address caller);
/**
* @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);
function initialize(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) public virtual initializer {
__TimelockController_init(minDelay, proposers, executors, admin);
}
/**
* @dev Initializes the contract with the following parameters:
*
* - `minDelay`: initial minimum delay in seconds 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.
*/
function __TimelockController_init(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) internal onlyInitializing {
__TimelockController_init_unchained(minDelay, proposers, executors, admin);
}
function __TimelockController_init_unchained(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) internal onlyInitializing {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
// self administration
_grantRole(DEFAULT_ADMIN_ROLE, address(this));
// optional admin
if (admin != address(0)) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
// register proposers and cancellers
for (uint256 i = 0; i < proposers.length; ++i) {
_grantRole(PROPOSER_ROLE, proposers[i]);
_grantRole(CANCELLER_ROLE, proposers[i]);
}
// register executors
for (uint256 i = 0; i < executors.length; ++i) {
_grantRole(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 virtual {}
/// @inheritdoc IERC165
function supportsInterface(
bytes4 interfaceId
) public view virtual override(AccessControlUpgradeable, ERC1155HolderUpgradeable) returns (bool) {
return super.supportsInterface(interfaceId);
}
/**
* @dev Returns whether an id corresponds to a registered operation. This
* includes both Waiting, Ready, and Done operations.
*/
function isOperation(bytes32 id) public view returns (bool) {
return getOperationState(id) != OperationState.Unset;
}
/**
* @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
*/
function isOperationPending(bytes32 id) public view returns (bool) {
OperationState state = getOperationState(id);
return state == OperationState.Waiting || state == OperationState.Ready;
}
/**
* @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
*/
function isOperationReady(bytes32 id) public view returns (bool) {
return getOperationState(id) == OperationState.Ready;
}
/**
* @dev Returns whether an operation is done or not.
*/
function isOperationDone(bytes32 id) public view returns (bool) {
return getOperationState(id) == OperationState.Done;
}
/**
* @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) {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
return $._timestamps[id];
}
/**
* @dev Returns operation state.
*/
function getOperationState(bytes32 id) public view virtual returns (OperationState) {
uint256 timestamp = getTimestamp(id);
if (timestamp == 0) {
return OperationState.Unset;
} else if (timestamp == _DONE_TIMESTAMP) {
return OperationState.Done;
} else if (timestamp > block.timestamp) {
return OperationState.Waiting;
} else {
return OperationState.Ready;
}
}
/**
* @dev Returns the minimum delay in seconds 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) {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
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) {
if (targets.length != values.length || targets.length != payloads.length) {
revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length);
}
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 {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
if (isOperation(id)) {
revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset));
}
uint256 minDelay = getMinDelay();
if (delay < minDelay) {
revert TimelockInsufficientDelay(delay, minDelay);
}
$._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) {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
if (!isOperationPending(id)) {
revert TimelockUnexpectedOperationState(
id,
_encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready)
);
}
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) {
if (targets.length != values.length || targets.length != payloads.length) {
revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length);
}
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, bytes memory returndata) = target.call{value: value}(data);
Address.verifyCallResult(success, returndata);
}
/**
* @dev Checks before execution of an operation's calls.
*/
function _beforeCall(bytes32 id, bytes32 predecessor) private view {
if (!isOperationReady(id)) {
revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready));
}
if (predecessor != bytes32(0) && !isOperationDone(predecessor)) {
revert TimelockUnexecutedPredecessor(predecessor);
}
}
/**
* @dev Checks after execution of an operation's calls.
*/
function _afterCall(bytes32 id) private {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
if (!isOperationReady(id)) {
revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.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 {
TimelockControllerStorage storage $ = _getTimelockControllerStorage();
address sender = _msgSender();
if (sender != address(this)) {
revert TimelockUnauthorizedCaller(sender);
}
emit MinDelayChange($._minDelay, newDelay);
$._minDelay = newDelay;
}
/**
* @dev Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to
* the underlying position in the `OperationState` enum. For example:
*
* 0x000...1000
* ^^^^^^----- ...
* ^---- Done
* ^--- Ready
* ^-- Waiting
* ^- Unset
*/
function _encodeStateBitmap(OperationState operationState) internal pure returns (bytes32) {
return bytes32(1 << uint8(operationState));
}
}
"
},
"lib/core/src/interfaces/common/IEntity.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEntity {
error NotInitialized();
/**
* @notice Get the factory's address.
* @return address of the factory
*/
function FACTORY() external view returns (address);
/**
* @notice Get the entity's type.
* @return type of the entity
*/
function TYPE() external view returns (uint64);
/**
* @notice Initialize this entity contract by using a given data.
* @param data some data to use
*/
function initialize(
bytes calldata data
) external;
}
"
},
"lib/core/src/interfaces/common/IRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IRegistry {
error EntityNotExist();
/**
* @notice Emitted when an entity is added.
* @param entity address of the added entity
*/
event AddEntity(address indexed entity);
/**
* @notice Get if a given address is an entity.
* @param account address to check
* @return if the given address is an entity
*/
function isEntity(
address account
) external view returns (bool);
/**
* @notice Get a total number of entities.
* @return total number of entities added
*/
function totalEntities() external view returns (uint256);
/**
* @notice Get an entity given its index.
* @param index index of the entity to get
* @return address of the entity
*/
function entity(
uint256 index
) external view returns (address);
}
"
},
"lib/network/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to comput
Submitted on: 2025-10-28 12:26:16
Comments
Log in to comment.
No comments yet.