Overview
ETH Balance
ETH Value
$0.00Latest 25 from a total of 17,461 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Collect | 427653181 | 3 mins ago | IN | 0 ETH | 0.00001234 | ||||
| Collect | 427653143 | 3 mins ago | IN | 0 ETH | 0.00001234 | ||||
| Collect | 427653106 | 3 mins ago | IN | 0 ETH | 0.00001235 | ||||
| Collect | 427653068 | 3 mins ago | IN | 0 ETH | 0.00001198 | ||||
| Collect | 427653030 | 3 mins ago | IN | 0 ETH | 0.00001243 | ||||
| Collect | 427652992 | 4 mins ago | IN | 0 ETH | 0.00001329 | ||||
| Collect | 427652955 | 4 mins ago | IN | 0 ETH | 0.00001201 | ||||
| Collect | 427652918 | 4 mins ago | IN | 0 ETH | 0.00001241 | ||||
| Collect | 427652881 | 4 mins ago | IN | 0 ETH | 0.00001232 | ||||
| Collect | 427652843 | 4 mins ago | IN | 0 ETH | 0.00001214 | ||||
| Collect | 427652804 | 4 mins ago | IN | 0 ETH | 0.00001277 | ||||
| Collect | 427652766 | 5 mins ago | IN | 0 ETH | 0.00001261 | ||||
| Collect | 427652729 | 5 mins ago | IN | 0 ETH | 0.00001213 | ||||
| Collect | 427652692 | 5 mins ago | IN | 0 ETH | 0.00001258 | ||||
| Collect | 427652655 | 5 mins ago | IN | 0 ETH | 0.00001224 | ||||
| Collect | 427652618 | 5 mins ago | IN | 0 ETH | 0.00001261 | ||||
| Collect | 427652581 | 5 mins ago | IN | 0 ETH | 0.00001252 | ||||
| Collect | 427652544 | 5 mins ago | IN | 0 ETH | 0.00001236 | ||||
| Collect | 427652507 | 6 mins ago | IN | 0 ETH | 0.00001264 | ||||
| Collect | 427652470 | 6 mins ago | IN | 0 ETH | 0.00001222 | ||||
| Collect | 427652433 | 6 mins ago | IN | 0 ETH | 0.00001299 | ||||
| Collect | 427652396 | 6 mins ago | IN | 0 ETH | 0.00001263 | ||||
| Collect | 427652358 | 6 mins ago | IN | 0 ETH | 0.00001225 | ||||
| Collect | 427652320 | 6 mins ago | IN | 0 ETH | 0.00001301 | ||||
| Collect | 427652282 | 7 mins ago | IN | 0 ETH | 0.00001301 |
Latest 1 internal transaction
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 397492865 | 87 days ago | Contract Creation | 0 ETH |
Cross-Chain Transactions
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.22;
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {ProxyAdmin} from "./ProxyAdmin.sol";
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
/// @dev See {UUPSUpgradeable-upgradeToAndCall}
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
* 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
* the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
* the proxy admin cannot fallback to the target implementation.
*
* These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
* dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
* call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
* allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
* interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
* meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
*
* IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
* immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
* overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
* undesirable state where the admin slot is different from the actual admin. Relying on the value of the admin slot
* is generally fine if the implementation is trusted.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
* compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
* function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
* could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
// An immutable address for the admin to avoid unnecessary SLOADs before each call
// at the expense of removing the ability to change the admin once it's set.
// This is acceptable if the admin is always a ProxyAdmin instance or similar contract
// with its own ability to transfer the permissions to another account.
address private immutable _admin;
/**
* @dev The proxy caller is the current admin, and can't fallback to the proxy target.
*/
error ProxyDeniedAdminAccess();
/**
* @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
* backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
* {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_admin = address(new ProxyAdmin(initialOwner));
// Set the storage value and emit an event for ERC-1967 compatibility
ERC1967Utils.changeAdmin(_proxyAdmin());
}
/**
* @dev Returns the admin of this proxy.
*/
function _proxyAdmin() internal view virtual returns (address) {
return _admin;
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
*/
function _fallback() internal virtual override {
if (msg.sender == _proxyAdmin()) {
if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
revert ProxyDeniedAdminAccess();
} else {
_dispatchUpgradeToAndCall();
}
} else {
super._fallback();
}
}
/**
* @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
function _dispatchUpgradeToAndCall() private {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImplementation, data);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IGraphToken is IERC20 {
// -- Mint and Burn --
function burn(uint256 amount) external;
function burnFrom(address _from, uint256 amount) external;
function mint(address _to, uint256 _amount) external;
// -- Mint Admin --
function addMinter(address _account) external;
function removeMinter(address _account) external;
function renounceMinter() external;
function isMinter(address _account) external view returns (bool);
// -- Permit --
function permit(
address _owner,
address _spender,
uint256 _value,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external;
// -- Allowance --
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
import { IGraphProxy } from "./IGraphProxy.sol";
/**
* @title Graph Upgradeable
* @dev This contract is intended to be inherited from upgradeable contracts.
*/
abstract contract GraphUpgradeable {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Check if the caller is the proxy admin.
*/
modifier onlyProxyAdmin(IGraphProxy _proxy) {
require(msg.sender == _proxy.admin(), "Caller must be the proxy admin");
_;
}
/**
* @dev Check if the caller is the implementation.
*/
modifier onlyImpl() {
require(msg.sender == _implementation(), "Only implementation");
_;
}
/**
* @dev Returns the current implementation.
* @return impl Address of the current implementation
*/
function _implementation() internal view returns (address impl) {
bytes32 slot = IMPLEMENTATION_SLOT;
// solhint-disable-next-line no-inline-assembly
assembly {
impl := sload(slot)
}
}
/**
* @notice Accept to be an implementation of proxy.
* @param _proxy Proxy to accept
*/
function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) {
_proxy.acceptUpgrade();
}
/**
* @notice Accept to be an implementation of proxy and then call a function from the new
* implementation as specified by `_data`, which should be an encoded function call. This is
* useful to initialize new storage variables in the proxied contract.
* @param _proxy Proxy to accept
* @param _data Calldata for the initialization function call (including selector)
*/
function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) external onlyProxyAdmin(_proxy) {
_proxy.acceptUpgradeAndCall(_data);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IGraphProxy {
function admin() external returns (address);
function setAdmin(address _newAdmin) external;
function implementation() external returns (address);
function pendingImplementation() external returns (address);
function upgradeTo(address _newImplementation) external;
function acceptUpgrade() external;
function acceptUpgradeAndCall(bytes calldata data) external;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
import { IGraphToken } from "../token/IGraphToken.sol";
/**
* @title TokenUtils library
* @notice This library contains utility functions for handling tokens (transfers and burns).
* It is specifically adapted for the GraphToken, so does not need to handle edge cases
* for other tokens.
*/
library TokenUtils {
/**
* @dev Pull tokens from an address to this contract.
* @param _graphToken Token to transfer
* @param _from Address sending the tokens
* @param _amount Amount of tokens to transfer
*/
function pullTokens(IGraphToken _graphToken, address _from, uint256 _amount) internal {
if (_amount > 0) {
require(_graphToken.transferFrom(_from, address(this), _amount), "!transfer");
}
}
/**
* @dev Push tokens from this contract to a receiving address.
* @param _graphToken Token to transfer
* @param _to Address receiving the tokens
* @param _amount Amount of tokens to transfer
*/
function pushTokens(IGraphToken _graphToken, address _to, uint256 _amount) internal {
if (_amount > 0) {
require(_graphToken.transfer(_to, _amount), "!transfer");
}
}
/**
* @dev Burn tokens held by this contract.
* @param _graphToken Token to burn
* @param _amount Amount of tokens to burn
*/
function burnTokens(IGraphToken _graphToken, uint256 _amount) internal {
if (_amount > 0) {
_graphToken.burn(_amount);
}
}
}// SPDX-License-Identifier: Apache-2.0 /* * Copyright 2020, Offchain Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Originally copied from: * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals * * MODIFIED from Offchain Labs' implementation: * - Changed solidity version to 0.7.6 ([email protected]) * */ pragma solidity ^0.7.6 || 0.8.27; interface ITokenGateway { /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated // event OutboundTransferInitiated( // address token, // address indexed _from, // address indexed _to, // uint256 indexed _transferId, // uint256 _amount, // bytes _data // ); /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized // event InboundTransferFinalized( // address token, // address indexed _from, // address indexed _to, // uint256 indexed _transferId, // uint256 _amount, // bytes _data // ); function outboundTransfer( address token, address to, uint256 amunt, uint256 maxas, uint256 gasPiceBid, bytes calldata data ) external payable returns (bytes memory); function finalizeInboundTransfer( address token, address from, address to, uint256 amount, bytes calldata data ) external payable; /** * @notice Calculate the address used when bridging an ERC20 token * @dev the L1 and L2 address oracles may not always be in sync. * For example, a custom token may have been registered but not deployed or the contract self destructed. * @param l1ERC20 address of L1 token * @return L2 address of a bridged ERC20 token */ function calculateL2TokenAddress(address l1ERC20) external view returns (address); }
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
/**
* @title Curation Interface
* @dev Interface for the Curation contract (and L2Curation too)
*/
interface ICuration {
// -- Configuration --
/**
* @notice Update the default reserve ratio to `defaultReserveRatio`
* @param defaultReserveRatio Reserve ratio (in PPM)
*/
function setDefaultReserveRatio(uint32 defaultReserveRatio) external;
/**
* @notice Update the minimum deposit amount needed to intialize a new subgraph
* @param minimumCurationDeposit Minimum amount of tokens required deposit
*/
function setMinimumCurationDeposit(uint256 minimumCurationDeposit) external;
/**
* @notice Set the curation tax percentage to charge when a curator deposits GRT tokens.
* @param percentage Curation tax percentage charged when depositing GRT tokens
*/
function setCurationTaxPercentage(uint32 percentage) external;
/**
* @notice Set the master copy to use as clones for the curation token.
* @param curationTokenMaster Address of implementation contract to use for curation tokens
*/
function setCurationTokenMaster(address curationTokenMaster) external;
// -- Curation --
/**
* @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool.
* @param subgraphDeploymentID Subgraph deployment pool from where to mint signal
* @param tokensIn Amount of Graph Tokens to deposit
* @param signalOutMin Expected minimum amount of signal to receive
* @return Amount of signal minted
* @return Amount of curation tax burned
*/
function mint(
bytes32 subgraphDeploymentID,
uint256 tokensIn,
uint256 signalOutMin
) external returns (uint256, uint256);
/**
* @notice Burn signal from the SubgraphDeployment curation pool
* @param subgraphDeploymentID SubgraphDeployment the curator is returning signal
* @param signalIn Amount of signal to return
* @param tokensOutMin Expected minimum amount of tokens to receive
* @return Tokens returned
*/
function burn(bytes32 subgraphDeploymentID, uint256 signalIn, uint256 tokensOutMin) external returns (uint256);
/**
* @notice Assign Graph Tokens collected as curation fees to the curation pool reserve.
* @param subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves
* @param tokens Amount of Graph Tokens to add to reserves
*/
function collect(bytes32 subgraphDeploymentID, uint256 tokens) external;
// -- Getters --
/**
* @notice Check if any GRT tokens are deposited for a SubgraphDeployment.
* @param subgraphDeploymentID SubgraphDeployment to check if curated
* @return True if curated, false otherwise
*/
function isCurated(bytes32 subgraphDeploymentID) external view returns (bool);
/**
* @notice Get the amount of signal a curator has in a curation pool.
* @param curator Curator owning the signal tokens
* @param subgraphDeploymentID Subgraph deployment curation pool
* @return Amount of signal owned by a curator for the subgraph deployment
*/
function getCuratorSignal(address curator, bytes32 subgraphDeploymentID) external view returns (uint256);
/**
* @notice Get the amount of signal in a curation pool.
* @param subgraphDeploymentID Subgraph deployment curation pool
* @return Amount of signal minted for the subgraph deployment
*/
function getCurationPoolSignal(bytes32 subgraphDeploymentID) external view returns (uint256);
/**
* @notice Get the amount of token reserves in a curation pool.
* @param subgraphDeploymentID Subgraph deployment curation pool
* @return Amount of token reserves in the curation pool
*/
function getCurationPoolTokens(bytes32 subgraphDeploymentID) external view returns (uint256);
/**
* @notice Calculate amount of signal that can be bought with tokens in a curation pool.
* This function considers and excludes the deposit tax.
* @param subgraphDeploymentID Subgraph deployment to mint signal
* @param tokensIn Amount of tokens used to mint signal
* @return Amount of signal that can be bought
* @return Amount of tokens that will be burned as curation tax
*/
function tokensToSignal(bytes32 subgraphDeploymentID, uint256 tokensIn) external view returns (uint256, uint256);
/**
* @notice Calculate number of tokens to get when burning signal from a curation pool.
* @param subgraphDeploymentID Subgraph deployment to burn signal
* @param signalIn Amount of signal to burn
* @return Amount of tokens to get for the specified amount of signal
*/
function signalToTokens(bytes32 subgraphDeploymentID, uint256 signalIn) external view returns (uint256);
/**
* @notice Tax charged when curators deposit funds.
* Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
* @return Curation tax percentage expressed in PPM
*/
function curationTaxPercentage() external view returns (uint32);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IEpochManager {
// -- Configuration --
function setEpochLength(uint256 epochLength) external;
// -- Epochs
function runEpoch() external;
// -- Getters --
function isCurrentEpochRun() external view returns (bool);
function blockNum() external view returns (uint256);
function blockHash(uint256 block) external view returns (bytes32);
function currentEpoch() external view returns (uint256);
function currentEpochBlock() external view returns (uint256);
function currentEpochBlockSinceStart() external view returns (uint256);
function epochsSince(uint256 epoch) external view returns (uint256);
function epochsSinceUpdate() external view returns (uint256);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IController {
function getGovernor() external view returns (address);
// -- Registry --
function setContractProxy(bytes32 id, address contractAddress) external;
function unsetContractProxy(bytes32 id) external;
function updateController(bytes32 id, address controller) external;
function getContractProxy(bytes32 id) external view returns (address);
// -- Pausing --
function setPartialPaused(bool partialPaused) external;
function setPaused(bool paused) external;
function setPauseGuardian(address newPauseGuardian) external;
function paused() external view returns (bool);
function partialPaused() external view returns (bool);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
/**
* @title IGoverned
* @dev Interface for the Governed contract.
*/
interface IGoverned {
// -- State getters --
function governor() external view returns (address);
function pendingGovernor() external view returns (address);
// -- External functions --
/**
* @notice Admin function to begin change of governor.
* @param newGovernor Address of new `governor`
*/
function transferOwnership(address newGovernor) external;
/**
* @notice Admin function for pending governor to accept role and update governor.
*/
function acceptOwnership() external;
// -- Events --
event NewPendingOwnership(address indexed from, address indexed to);
event NewOwnership(address indexed from, address indexed to);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
import { IController } from "./IController.sol";
/**
* @title Managed Interface
* @dev Interface for contracts that can be managed by a controller.
*/
interface IManaged {
/**
* @notice Set the controller that manages this contract
* @dev Only the current controller can set a new controller
* @param controller Address of the new controller
*/
function setController(address controller) external;
/**
* @notice Sync protocol contract addresses from the Controller registry
* @dev This function will cache all the contracts using the latest addresses.
* Anyone can call the function whenever a Proxy contract change in the
* controller to ensure the protocol is using the latest version.
*/
function syncAllContracts() external;
/**
* @notice Get the Controller that manages this contract
* @return The Controller as an IController interface
*/
function controller() external view returns (IController);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IRewardsIssuer {
/**
* @dev Get allocation data to calculate rewards issuance
*
* @param allocationId The allocation Id
* @return isActive Whether the allocation is active or not
* @return indexer The indexer address
* @return subgraphDeploymentId Subgraph deployment id for the allocation
* @return tokens Amount of allocated tokens
* @return accRewardsPerAllocatedToken Rewards snapshot
* @return accRewardsPending Snapshot of accumulated rewards from previous allocation resizing, pending to be claimed
*/
function getAllocationData(
address allocationId
)
external
view
returns (
bool isActive,
address indexer,
bytes32 subgraphDeploymentId,
uint256 tokens,
uint256 accRewardsPerAllocatedToken,
uint256 accRewardsPending
);
/**
* @notice Return the total amount of tokens allocated to subgraph.
* @param subgraphDeploymentId Deployment Id for the subgraph
* @return Total tokens allocated to subgraph
*/
function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentId) external view returns (uint256);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IRewardsManager {
/**
* @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment.
*/
struct Subgraph {
uint256 accRewardsForSubgraph;
uint256 accRewardsForSubgraphSnapshot;
uint256 accRewardsPerSignalSnapshot;
uint256 accRewardsPerAllocatedToken;
}
// -- Config --
function setIssuancePerBlock(uint256 issuancePerBlock) external;
function setMinimumSubgraphSignal(uint256 minimumSubgraphSignal) external;
function setSubgraphService(address subgraphService) external;
// -- Denylist --
function setSubgraphAvailabilityOracle(address subgraphAvailabilityOracle) external;
function setDenied(bytes32 subgraphDeploymentID, bool deny) external;
function isDenied(bytes32 subgraphDeploymentID) external view returns (bool);
// -- Getters --
function getNewRewardsPerSignal() external view returns (uint256);
function getAccRewardsPerSignal() external view returns (uint256);
function getAccRewardsForSubgraph(bytes32 subgraphDeploymentID) external view returns (uint256);
function getAccRewardsPerAllocatedToken(bytes32 subgraphDeploymentID) external view returns (uint256, uint256);
function getRewards(address rewardsIssuer, address allocationID) external view returns (uint256);
function calcRewards(uint256 tokens, uint256 accRewardsPerAllocatedToken) external pure returns (uint256);
// -- Updates --
function updateAccRewardsPerSignal() external returns (uint256);
function takeRewards(address allocationID) external returns (uint256);
// -- Hooks --
function onSubgraphSignalUpdate(bytes32 subgraphDeploymentID) external returns (uint256);
function onSubgraphAllocationUpdate(bytes32 subgraphDeploymentID) external returns (uint256);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;
interface IGraphProxy {
function admin() external returns (address);
function setAdmin(address newAdmin) external;
function implementation() external returns (address);
function pendingImplementation() external returns (address);
function upgradeTo(address newImplementation) external;
function acceptUpgrade() external;
function acceptUpgradeAndCall(bytes calldata data) external;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IGraphProxy } from "./IGraphProxy.sol";
import { IGoverned } from "../governance/IGoverned.sol";
/**
* @title IGraphProxyAdmin
* @dev GraphProxyAdmin contract interface
* @dev Note that this interface is not used by the contract implementation, just used for types and abi generation
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IGraphProxyAdmin is IGoverned {
function getProxyImplementation(IGraphProxy proxy) external view returns (address);
function getProxyPendingImplementation(IGraphProxy proxy) external view returns (address);
function getProxyAdmin(IGraphProxy proxy) external view returns (address);
function changeProxyAdmin(IGraphProxy proxy, address newAdmin) external;
function upgrade(IGraphProxy proxy, address implementation) external;
function upgradeTo(IGraphProxy proxy, address implementation) external;
function upgradeToAndCall(IGraphProxy proxy, address implementation, bytes calldata data) external;
function acceptProxy(IGraphProxy proxy) external;
function acceptProxyAndCall(IGraphProxy proxy, bytes calldata data) external;
// storage
function governor() external view returns (address);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IGraphPayments } from "../horizon/IGraphPayments.sol";
/**
* @title Interface of the base {DataService} contract as defined by the Graph Horizon specification.
* @notice This interface provides a guardrail for contracts that use the Data Service framework
* to implement a data service on Graph Horizon. Much of the specification is intentionally loose
* to allow for greater flexibility when designing a data service. It's not possible to guarantee that
* an implementation will honor the Data Service framework guidelines so it's advised to always review
* the implementation code and the documentation.
* @dev This interface is expected to be inherited and extended by a data service interface. It can be
* used to interact with it however it's advised to use the more specific parent interface.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IDataService {
/**
* @notice Emitted when a service provider is registered with the data service.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
event ServiceProviderRegistered(address indexed serviceProvider, bytes data);
/**
* @notice Emitted when a service provider accepts a provision in {Graph Horizon staking contract}.
* @param serviceProvider The address of the service provider.
*/
event ProvisionPendingParametersAccepted(address indexed serviceProvider);
/**
* @notice Emitted when a service provider starts providing the service.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
event ServiceStarted(address indexed serviceProvider, bytes data);
/**
* @notice Emitted when a service provider stops providing the service.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
event ServiceStopped(address indexed serviceProvider, bytes data);
/**
* @notice Emitted when a service provider collects payment.
* @param serviceProvider The address of the service provider.
* @param feeType The type of fee to collect as defined in {GraphPayments}.
* @param tokens The amount of tokens collected.
*/
event ServicePaymentCollected(
address indexed serviceProvider,
IGraphPayments.PaymentTypes indexed feeType,
uint256 tokens
);
/**
* @notice Emitted when a service provider is slashed.
* @param serviceProvider The address of the service provider.
* @param tokens The amount of tokens slashed.
*/
event ServiceProviderSlashed(address indexed serviceProvider, uint256 tokens);
/**
* @notice Registers a service provider with the data service. The service provider can now
* start providing the service.
* @dev Before registering, the service provider must have created a provision in the
* Graph Horizon staking contract with parameters that are compatible with the data service.
*
* Verifies provision parameters and rejects registration in the event they are not valid.
*
* Emits a {ServiceProviderRegistered} event.
*
* NOTE: Failing to accept the provision will result in the service provider operating
* on an unverified provision. Depending on of the data service this can be a security
* risk as the protocol won't be able to guarantee economic security for the consumer.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
function register(address serviceProvider, bytes calldata data) external;
/**
* @notice Accepts pending parameters in the provision of a service provider in the {Graph Horizon staking
* contract}.
* @dev Provides a way for the data service to validate and accept provision parameter changes. Call {_acceptProvision}.
*
* Emits a {ProvisionPendingParametersAccepted} event.
*
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
function acceptProvisionPendingParameters(address serviceProvider, bytes calldata data) external;
/**
* @notice Service provider starts providing the service.
* @dev Emits a {ServiceStarted} event.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
function startService(address serviceProvider, bytes calldata data) external;
/**
* @notice Service provider stops providing the service.
* @dev Emits a {ServiceStopped} event.
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
function stopService(address serviceProvider, bytes calldata data) external;
/**
* @notice Collects payment earnt by the service provider.
* @dev The implementation of this function is expected to interact with {GraphPayments}
* to collect payment from the service payer, which is done via {IGraphPayments-collect}.
*
* Emits a {ServicePaymentCollected} event.
*
* NOTE: Data services that are vetted by the Graph Council might qualify for a portion of
* protocol issuance to cover for these payments. In this case, the funds are taken by
* interacting with the rewards manager contract instead of the {GraphPayments} contract.
* @param serviceProvider The address of the service provider.
* @param feeType The type of fee to collect as defined in {GraphPayments}.
* @param data Custom data, usage defined by the data service.
* @return The amount of tokens collected.
*/
function collect(
address serviceProvider,
IGraphPayments.PaymentTypes feeType,
bytes calldata data
) external returns (uint256);
/**
* @notice Slash a service provider for misbehaviour.
* @dev To slash the service provider's provision the function should call
* {Staking-slash}.
*
* Emits a {ServiceProviderSlashed} event.
*
* @param serviceProvider The address of the service provider.
* @param data Custom data, usage defined by the data service.
*/
function slash(address serviceProvider, bytes calldata data) external;
/**
* @notice External getter for the thawing period range
* @return Minimum thawing period allowed
* @return Maximum thawing period allowed
*/
function getThawingPeriodRange() external view returns (uint64, uint64);
/**
* @notice External getter for the verifier cut range
* @return Minimum verifier cut allowed
* @return Maximum verifier cut allowed
*/
function getVerifierCutRange() external view returns (uint32, uint32);
/**
* @notice External getter for the provision tokens range
* @return Minimum provision tokens allowed
* @return Maximum provision tokens allowed
*/
function getProvisionTokensRange() external view returns (uint256, uint256);
/**
* @notice External getter for the delegation ratio
* @return The delegation ratio
*/
function getDelegationRatio() external view returns (uint32);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataService } from "./IDataService.sol";
/**
* @title Interface for the {DataServiceFees} contract.
* @notice Extension for the {IDataService} contract to handle payment collateralization
* using a Horizon provision.
*
* It's designed to be used with the Data Service framework:
* - When a service provider collects payment with {IDataService.collect} the data service should lock
* stake to back the payment using {_lockStake}.
* - Every time there is a payment collection with {IDataService.collect}, the data service should
* attempt to release any expired stake claims by calling {_releaseStake}.
* - Stake claims can also be manually released by calling {releaseStake} directly.
*
* @dev Note that this implementation uses the entire provisioned stake as collateral for the payment.
* It can be used to provide economic security for the payments collected as long as the provisioned
* stake is not being used for other purposes.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IDataServiceFees is IDataService {
/**
* @notice A stake claim, representing provisioned stake that gets locked
* to be released to a service provider.
* @dev StakeClaims are stored in linked lists by service provider, ordered by
* creation timestamp.
* @param tokens The amount of tokens to be locked in the claim
* @param createdAt The timestamp when the claim was created
* @param releasableAt The timestamp when the tokens can be released
* @param nextClaim The next claim in the linked list
*/
struct StakeClaim {
uint256 tokens;
uint256 createdAt;
uint256 releasableAt;
bytes32 nextClaim;
}
/**
* @notice Emitted when a stake claim is created and stake is locked.
* @param serviceProvider The address of the service provider
* @param claimId The id of the stake claim
* @param tokens The amount of tokens to lock in the claim
* @param unlockTimestamp The timestamp when the tokens can be released
*/
event StakeClaimLocked(
address indexed serviceProvider,
bytes32 indexed claimId,
uint256 tokens,
uint256 unlockTimestamp
);
/**
* @notice Emitted when a stake claim is released and stake is unlocked.
* @param serviceProvider The address of the service provider
* @param claimId The id of the stake claim
* @param tokens The amount of tokens released
* @param releasableAt The timestamp when the tokens were released
*/
event StakeClaimReleased(
address indexed serviceProvider,
bytes32 indexed claimId,
uint256 tokens,
uint256 releasableAt
);
/**
* @notice Emitted when a series of stake claims are released.
* @param serviceProvider The address of the service provider
* @param claimsCount The number of stake claims being released
* @param tokensReleased The total amount of tokens being released
*/
event StakeClaimsReleased(address indexed serviceProvider, uint256 claimsCount, uint256 tokensReleased);
/**
* @notice Thrown when attempting to get a stake claim that does not exist.
* @param claimId The id of the stake claim
*/
error DataServiceFeesClaimNotFound(bytes32 claimId);
/**
* @notice Emitted when trying to lock zero tokens in a stake claim
*/
error DataServiceFeesZeroTokens();
/**
* @notice Releases expired stake claims for the caller.
* @dev This function is only meant to be called if the service provider has enough
* stake claims that releasing them all at once would exceed the block gas limit.
* @dev This function can be overriden and/or disabled.
* @dev Emits a {StakeClaimsReleased} event, and a {StakeClaimReleased} event for each claim released.
* @param numClaimsToRelease Amount of stake claims to process. If 0, all stake claims are processed.
*/
function releaseStake(uint256 numClaimsToRelease) external;
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataService } from "./IDataService.sol";
/**
* @title Interface for the {DataServicePausable} contract.
* @notice Extension for the {IDataService} contract, adds pausing functionality
* to the data service. Pausing is controlled by privileged accounts called
* pause guardians.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IDataServicePausable is IDataService {
/**
* @notice Emitted when a pause guardian is set.
* @param account The address of the pause guardian
* @param allowed The allowed status of the pause guardian
*/
event PauseGuardianSet(address indexed account, bool allowed);
/**
* @notice Emitted when a the caller is not a pause guardian
* @param account The address of the pause guardian
*/
error DataServicePausableNotPauseGuardian(address account);
/**
* @notice Emitted when a pause guardian is set to the same allowed status
* @param account The address of the pause guardian
* @param allowed The allowed status of the pause guardian
*/
error DataServicePausablePauseGuardianNoChange(address account, bool allowed);
/**
* @notice Pauses the data service.
* @dev Note that only functions using the modifiers `whenNotPaused`
* and `whenPaused` will be affected by the pause.
*
* Requirements:
* - The contract must not be already paused
*/
function pause() external;
/**
* @notice Unpauses the data service.
* @dev Note that only functions using the modifiers `whenNotPaused`
* and `whenPaused` will be affected by the pause.
*
* Requirements:
* - The contract must be paused
*/
function unpause() external;
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataService } from "./IDataService.sol";
/**
* @title Interface for the {IDataServicePausable} contract.
* @notice Extension for the {IDataService} contract, adds the ability to rescue
* any ERC20 token or ETH from the contract, controlled by a rescuer privileged role.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IDataServiceRescuable is IDataService {
/**
* @notice Emitted when tokens are rescued from the contract.
* @param from The address initiating the rescue
* @param to The address receiving the rescued tokens
* @param token The address of the token being rescued
* @param tokens The amount of tokens rescued
*/
event TokensRescued(address indexed from, address indexed to, address indexed token, uint256 tokens);
/**
* @notice Emitted when a rescuer is set.
* @param account The address of the rescuer
* @param allowed Whether the rescuer is allowed to rescue tokens
*/
event RescuerSet(address indexed account, bool allowed);
/**
* @notice Thrown when trying to rescue zero tokens.
*/
error DataServiceRescuableCannotRescueZero();
/**
* @notice Thrown when the caller is not a rescuer.
* @param account The address of the account that attempted the rescue
*/
error DataServiceRescuableNotRescuer(address account);
/**
* @notice Rescues GRT tokens from the contract.
* @dev Declared as virtual to allow disabling the function via override.
*
* Requirements:
* - Cannot rescue zero tokens.
*
* Emits a {TokensRescued} event.
*
* @param to Address of the tokens recipient.
* @param tokens Amount of tokens to rescue.
*/
function rescueGRT(address to, uint256 tokens) external;
/**
* @notice Rescues ether from the contract.
* @dev Declared as virtual to allow disabling the function via override.
*
* Requirements:
* - Cannot rescue zeroether.
*
* Emits a {TokensRescued} event.
*
* @param to Address of the tokens recipient.
* @param tokens Amount of tokens to rescue.
*/
function rescueETH(address payable to, uint256 tokens) external;
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
/**
* @title Interface for the {Authorizable} contract
* @notice Implements an authorization scheme that allows authorizers to
* authorize signers to sign on their behalf.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IAuthorizable {
/**
* @notice Details for an authorizer-signer pair
* @dev Authorizations can be removed only after a thawing period
* @param authorizer The address of the authorizer - resource owner
* @param thawEndTimestamp The timestamp at which the thawing period ends (zero if not thawing)
* @param revoked Whether the signer authorization was revoked
*/
struct Authorization {
address authorizer;
uint256 thawEndTimestamp;
bool revoked;
}
/**
* @notice Emitted when a signer is authorized to sign for a authorizer
* @param authorizer The address of the authorizer
* @param signer The address of the signer
*/
event SignerAuthorized(address indexed authorizer, address indexed signer);
/**
* @notice Emitted when a signer is thawed to be de-authorized
* @param authorizer The address of the authorizer thawing the signer
* @param signer The address of the signer to thaw
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
event SignerThawing(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);
/**
* @notice Emitted when the thawing of a signer is cancelled
* @param authorizer The address of the authorizer cancelling the thawing
* @param signer The address of the signer
* @param thawEndTimestamp The timestamp at which the thawing period was scheduled to end
*/
event SignerThawCanceled(address indexed authorizer, address indexed signer, uint256 thawEndTimestamp);
/**
* @notice Emitted when a signer has been revoked after thawing
* @param authorizer The address of the authorizer revoking the signer
* @param signer The address of the signer
*/
event SignerRevoked(address indexed authorizer, address indexed signer);
/**
* @notice Thrown when attempting to authorize a signer that is already authorized
* @param authorizer The address of the authorizer
* @param signer The address of the signer
* @param revoked The revoked status of the authorization
*/
error AuthorizableSignerAlreadyAuthorized(address authorizer, address signer, bool revoked);
/**
* @notice Thrown when the signer proof deadline is invalid
* @param proofDeadline The deadline for the proof provided
* @param currentTimestamp The current timestamp
*/
error AuthorizableInvalidSignerProofDeadline(uint256 proofDeadline, uint256 currentTimestamp);
/**
* @notice Thrown when the signer proof is invalid
*/
error AuthorizableInvalidSignerProof();
/**
* @notice Thrown when the signer is not authorized by the authorizer
* @param authorizer The address of the authorizer
* @param signer The address of the signer
*/
error AuthorizableSignerNotAuthorized(address authorizer, address signer);
/**
* @notice Thrown when the signer is not thawing
* @param signer The address of the signer
*/
error AuthorizableSignerNotThawing(address signer);
/**
* @notice Thrown when the signer is still thawing
* @param currentTimestamp The current timestamp
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
error AuthorizableSignerStillThawing(uint256 currentTimestamp, uint256 thawEndTimestamp);
/**
* @notice The period after which a signer can be revoked after thawing
* @return The period in seconds
*/
function REVOKE_AUTHORIZATION_THAWING_PERIOD() external view returns (uint256);
/**
* @notice Authorize a signer to sign on behalf of the authorizer
* @dev Requirements:
* - `signer` must not be already authorized
* - `proofDeadline` must be greater than the current timestamp
* - `proof` must be a valid signature from the signer being authorized
*
* Emits a {SignerAuthorized} event
* @param signer The address of the signer
* @param proofDeadline The deadline for the proof provided by the signer
* @param proof The proof provided by the signer to be authorized by the authorizer
* consists of (chain id, verifying contract address, domain, proof deadline, authorizer address)
*/
function authorizeSigner(address signer, uint256 proofDeadline, bytes calldata proof) external;
/**
* @notice Starts thawing a signer to be de-authorized
* @dev Thawing a signer signals that signatures from that signer will soon be deemed invalid.
* Once a signer is thawed, they should be viewed as revoked regardless of their revocation status.
* If a signer is already thawing and this function is called, the thawing period is reset.
* Requirements:
* - `signer` must be authorized by the authorizer calling this function
*
* Emits a {SignerThawing} event
* @param signer The address of the signer to thaw
*/
function thawSigner(address signer) external;
/**
* @notice Stops thawing a signer.
* @dev Requirements:
* - `signer` must be thawing and authorized by the function caller
*
* Emits a {SignerThawCanceled} event
* @param signer The address of the signer to cancel thawing
*/
function cancelThawSigner(address signer) external;
/**
* @notice Revokes a signer if thawed.
* @dev Requirements:
* - `signer` must be thawed and authorized by the function caller
*
* Emits a {SignerRevoked} event
* @param signer The address of the signer
*/
function revokeAuthorizedSigner(address signer) external;
/**
* @notice Returns the timestamp at which the thawing period ends for a signer.
* Returns 0 if the signer is not thawing.
* @param signer The address of the signer
* @return The timestamp at which the thawing period ends
*/
function getThawEnd(address signer) external view returns (uint256);
/**
* @notice Returns true if the signer is authorized by the authorizer
* @param authorizer The address of the authorizer
* @param signer The address of the signer
* @return true if the signer is authorized by the authorizer, false otherwise
*/
function isAuthorized(address authorizer, address signer) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
/**
* @title Interface for the {GraphPayments} contract
* @notice This contract is part of the Graph Horizon payments protocol. It's designed
* to pull funds (GRT) from the {PaymentsEscrow} and distribute them according to a
* set of pre established rules.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IGraphPayments {
/**
* @notice Types of payments that are supported by the payments protocol
* @dev
*/
enum PaymentTypes {
QueryFee,
IndexingFee,
IndexingRewards
}
/**
* @notice Emitted when a payment is collected
* @param paymentType The type of payment as defined in {IGraphPayments}
* @param payer The address of the payer
* @param receiver The address of the receiver
* @param dataService The address of the data service
* @param tokens The total amount of tokens being collected
* @param tokensProtocol Amount of tokens charged as protocol tax
* @param tokensDataService Amount of tokens for the data service
* @param tokensDelegationPool Amount of tokens for delegators
* @param tokensReceiver Amount of tokens for the receiver
* @param receiverDestination The address where the receiver's payment cut is sent.
*/
event GraphPaymentCollected(
PaymentTypes indexed paymentType,
address indexed payer,
address receiver,
address indexed dataService,
uint256 tokens,
uint256 tokensProtocol,
uint256 tokensDataService,
uint256 tokensDelegationPool,
uint256 tokensReceiver,
address receiverDestination
);
/**
* @notice Thrown when the protocol payment cut is invalid
* @param protocolPaymentCut The protocol payment cut
*/
error GraphPaymentsInvalidProtocolPaymentCut(uint256 protocolPaymentCut);
/**
* @notice Thrown when trying to use a cut that is not expressed in PPM
* @param cut The cut
*/
error GraphPaymentsInvalidCut(uint256 cut);
/**
* @notice Returns the protocol payment cut
* @return The protocol payment cut in PPM
*/
function PROTOCOL_PAYMENT_CUT() external view returns (uint256);
/**
* @notice Initialize the contract
*/
function initialize() external;
/**
* @notice Collects funds from a payer.
* It will pay cuts to all relevant parties and forward the rest to the receiver destination address. If the
* destination address is zero the funds are automatically staked to the receiver. Note that the receiver
* destination address can be set to the receiver address to collect funds on the receiver without re-staking.
*
* Note that the collected amount can be zero.
*
* @param paymentType The type of payment as defined in {IGraphPayments}
* @param receiver The address of the receiver
* @param tokens The amount of tokens being collected.
* @param dataService The address of the data service
* @param dataServiceCut The data service cut in PPM
* @param receiverDestination The address where the receiver's payment cut is sent.
*/
function collect(
PaymentTypes paymentType,
address receiver,
uint256 tokens,
address dataService,
uint256 dataServiceCut,
address receiverDestination
) external;
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IPaymentsCollector } from "./IPaymentsCollector.sol";
import { IGraphPayments } from "./IGraphPayments.sol";
import { IAuthorizable } from "./IAuthorizable.sol";
/**
* @title Interface for the {GraphTallyCollector} contract
* @dev Implements the {IPaymentCollector} interface as defined by the Graph
* Horizon payments protocol.
* @notice Implements a payments collector contract that can be used to collect
* payments using a GraphTally RAV (Receipt Aggregate Voucher).
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IGraphTallyCollector is IPaymentsCollector, IAuthorizable {
/**
* @notice The Receipt Aggregate Voucher (RAV) struct
* @param collectionId The ID of the collection "bucket" the RAV belongs to. Note that multiple RAVs can be collected for the same collection id.
* @param payer The address of the payer the RAV was issued by
* @param serviceProvider The address of the service provider the RAV was issued to
* @param dataService The address of the data service the RAV was issued to
* @param timestampNs The RAV timestamp, indicating the latest GraphTally Receipt in the RAV
* @param valueAggregate The total amount owed to the service provider since the beginning of the payer-service provider relationship, including all debt that is already paid for.
* @param metadata Arbitrary metadata to extend functionality if a data service requires it
*/
struct ReceiptAggregateVoucher {
bytes32 collectionId;
address payer;
address serviceProvider;
address dataService;
uint64 timestampNs;
uint128 valueAggregate;
bytes metadata;
}
/**
* @notice A struct representing a signed RAV
* @param rav The RAV
* @param signature The signature of the RAV - 65 bytes: r (32 Bytes) || s (32 Bytes) || v (1 Byte)
*/
struct SignedRAV {
ReceiptAggregateVoucher rav;
bytes signature;
}
/**
* @notice Emitted when a RAV is collected
* @param collectionId The ID of the collection "bucket" the RAV belongs to.
* @param payer The address of the payer
* @param dataService The address of the data service
* @param serviceProvider The address of the service provider
* @param timestampNs The timestamp of the RAV
* @param valueAggregate The total amount owed to the service provider
* @param metadata Arbitrary metadata
* @param signature The signature of the RAV
*/
event RAVCollected(
bytes32 indexed collectionId,
address indexed payer,
address serviceProvider,
address indexed dataService,
uint64 timestampNs,
uint128 valueAggregate,
bytes metadata,
bytes signature
);
/**
* @notice Thrown when the RAV signer is invalid
*/
error GraphTallyCollectorInvalidRAVSigner();
/**
* @notice Thrown when the RAV is for a data service the service provider has no provision for
* @param dataService The address of the data service
*/
error GraphTallyCollectorUnauthorizedDataService(address dataService);
/**
* @notice Thrown when the caller is not the data service the RAV was issued to
* @param caller The address of the caller
* @param dataService The address of the data service
*/
error GraphTallyCollectorCallerNotDataService(address caller, address dataService);
/**
* @notice Thrown when the tokens collected are inconsistent with the collection history
* Each RAV should have a value greater than the previous one
* @param tokens The amount of tokens in the RAV
* @param tokensCollected The amount of tokens already collected
*/
error GraphTallyCollectorInconsistentRAVTokens(uint256 tokens, uint256 tokensCollected);
/**
* @notice Thrown when the attempting to collect more tokens than what it's owed
* @param tokensToCollect The amount of tokens to collect
* @param maxTokensToCollect The maximum amount of tokens to collect
*/
error GraphTallyCollectorInvalidTokensToCollectAmount(uint256 tokensToCollect, uint256 maxTokensToCollect);
/**
* @notice See {IPaymentsCollector.collect}
* This variant adds the ability to partially collect a RAV by specifying the amount of tokens to collect.
*
* Requirements:
* - The amount of tokens to collect must be less than or equal to the total amount of tokens in the RAV minus
* the tokens already collected.
* @param paymentType The payment type to collect
* @param data Additional data required for the payment collection. Encoded as follows:
* - SignedRAV `signedRAV`: The signed RAV
* - uint256 `dataServiceCut`: The data service cut in PPM
* - address `receiverDestination`: The address where the receiver's payment should be sent.
* @param tokensToCollect The amount of tokens to collect
* @return The amount of tokens collected
*/
function collect(
IGraphPayments.PaymentTypes paymentType,
bytes calldata data,
uint256 tokensToCollect
) external returns (uint256);
/**
* @dev Recovers the signer address of a signed ReceiptAggregateVoucher (RAV).
* @param signedRAV The SignedRAV containing the RAV and its signature.
* @return The address of the signer.
*/
function recoverRAVSigner(SignedRAV calldata signedRAV) external view returns (address);
/**
* @dev Computes the hash of a ReceiptAggregateVoucher (RAV).
* @param rav The RAV for which to compute the hash.
* @return The hash of the RAV.
*/
function encodeRAV(ReceiptAggregateVoucher calldata rav) external view returns (bytes32);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IHorizonStakingTypes } from "./internal/IHorizonStakingTypes.sol";
import { IHorizonStakingMain } from "./internal/IHorizonStakingMain.sol";
import { IHorizonStakingBase } from "./internal/IHorizonStakingBase.sol";
import { IHorizonStakingExtension } from "./internal/IHorizonStakingExtension.sol";
/**
* @title Complete interface for the Horizon Staking contract
* @notice This interface exposes all functions implemented by the {HorizonStaking} contract and its extension
* {HorizonStakingExtension} as well as the custom data types used by the contract.
* @dev Use this interface to interact with the Horizon Staking contract.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IHorizonStaking is IHorizonStakingTypes, IHorizonStakingBase, IHorizonStakingMain, IHorizonStakingExtension {}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IHorizonStakingTypes } from "./IHorizonStakingTypes.sol";
import { IGraphPayments } from "../IGraphPayments.sol";
import { ILinkedList } from "./ILinkedList.sol";
/**
* @title Interface for the {HorizonStakingBase} contract.
* @notice Provides getters for {HorizonStaking} and {HorizonStakingExtension} storage variables.
* @dev Most functions operate over {HorizonStaking} provisions. To uniquely identify a provision
* functions take `serviceProvider` and `verifier` addresses.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IHorizonStakingBase {
/**
* @notice Emitted when a service provider stakes tokens.
* @dev TRANSITION PERIOD: After transition period move to IHorizonStakingMain. Temporarily it
* needs to be here since it's emitted by {_stake} which is used by both {HorizonStaking}
* and {HorizonStakingExtension}.
* @param serviceProvider The address of the service provider.
* @param tokens The amount of tokens staked.
*/
event HorizonStakeDeposited(address indexed serviceProvider, uint256 tokens);
/**
* @notice Thrown when using an invalid thaw request type.
*/
error HorizonStakingInvalidThawRequestType();
/**
* @notice Gets the details of a service provider.
* @param serviceProvider The address of the service provider.
* @return The service provider details.
*/
function getServiceProvider(
address serviceProvider
) external view returns (IHorizonStakingTypes.ServiceProvider memory);
/**
* @notice Gets the stake of a service provider.
* @param serviceProvider The address of the service provider.
* @return The amount of tokens staked.
*/
function getStake(address serviceProvider) external view returns (uint256);
/**
* @notice Gets the service provider's idle stake which is the stake that is not being
* used for any provision. Note that this only includes service provider's self stake.
* @param serviceProvider The address of the service provider.
* @return The amount of tokens that are idle.
*/
function getIdleStake(address serviceProvider) external view returns (uint256);
/**
* @notice Gets the details of delegation pool.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @return The delegation pool details.
*/
function getDelegationPool(
address serviceProvider,
address verifier
) external view returns (IHorizonStakingTypes.DelegationPool memory);
/**
* @notice Gets the details of a delegation.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param delegator The address of the delegator.
* @return The delegation details.
*/
function getDelegation(
address serviceProvider,
address verifier,
address delegator
) external view returns (IHorizonStakingTypes.Delegation memory);
/**
* @notice Gets the delegation fee cut for a payment type.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param paymentType The payment type as defined by {IGraphPayments.PaymentTypes}.
* @return The delegation fee cut in PPM.
*/
function getDelegationFeeCut(
address serviceProvider,
address verifier,
IGraphPayments.PaymentTypes paymentType
) external view returns (uint256);
/**
* @notice Gets the details of a provision.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @return The provision details.
*/
function getProvision(
address serviceProvider,
address verifier
) external view returns (IHorizonStakingTypes.Provision memory);
/**
* @notice Gets the tokens available in a provision.
* Tokens available are the tokens in a provision that are not thawing. Includes service
* provider's and delegator's stake.
*
* Allows specifying a `delegationRatio` which caps the amount of delegated tokens that are
* considered available.
*
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param delegationRatio The delegation ratio.
* @return The amount of tokens available.
*/
function getTokensAvailable(
address serviceProvider,
address verifier,
uint32 delegationRatio
) external view returns (uint256);
/**
* @notice Gets the service provider's tokens available in a provision.
* @dev Calculated as the tokens available minus the tokens thawing.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @return The amount of tokens available.
*/
function getProviderTokensAvailable(address serviceProvider, address verifier) external view returns (uint256);
/**
* @notice Gets the delegator's tokens available in a provision.
* @dev Calculated as the tokens available minus the tokens thawing.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @return The amount of tokens available.
*/
function getDelegatedTokensAvailable(address serviceProvider, address verifier) external view returns (uint256);
/**
* @notice Gets a thaw request.
* @param thawRequestType The type of thaw request.
* @param thawRequestId The id of the thaw request.
* @return The thaw request details.
*/
function getThawRequest(
IHorizonStakingTypes.ThawRequestType thawRequestType,
bytes32 thawRequestId
) external view returns (IHorizonStakingTypes.ThawRequest memory);
/**
* @notice Gets the metadata of a thaw request list.
* Service provider and delegators each have their own thaw request list per provision.
* Metadata includes the head and tail of the list, plus the total number of thaw requests.
* @param thawRequestType The type of thaw request.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
* @return The thaw requests list metadata.
*/
function getThawRequestList(
IHorizonStakingTypes.ThawRequestType thawRequestType,
address serviceProvider,
address verifier,
address owner
) external view returns (ILinkedList.List memory);
/**
* @notice Gets the amount of thawed tokens that can be releasedfor a given provision.
* @dev Note that the value returned by this function does not return the total amount of thawed tokens
* but only those that can be released. If thaw requests are created with different thawing periods it's
* possible that an unexpired thaw request temporarily blocks the release of other ones that have already
* expired. This function will stop counting when it encounters the first thaw request that is not yet expired.
* @param thawRequestType The type of thaw request.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
* @param owner The owner of the thaw requests. Use either the service provider or delegator address.
* @return The amount of thawed tokens.
*/
function getThawedTokens(
IHorizonStakingTypes.ThawRequestType thawRequestType,
address serviceProvider,
address verifier,
address owner
) external view returns (uint256);
/**
* @notice Gets the maximum allowed thawing period for a provision.
* @return The maximum allowed thawing period in seconds.
*/
function getMaxThawingPeriod() external view returns (uint64);
/**
* @notice Return true if the verifier is an allowed locked verifier.
* @param verifier Address of the verifier
* @return True if verifier is allowed locked verifier, false otherwise
*/
function isAllowedLockedVerifier(address verifier) external view returns (bool);
/**
* @notice Return true if delegation slashing is enabled, false otherwise.
* @return True if delegation slashing is enabled, false otherwise
*/
function isDelegationSlashingEnabled() external view returns (bool);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IRewardsIssuer } from "../../contracts/rewards/IRewardsIssuer.sol";
/**
* @title Interface for {HorizonStakingExtension} contract.
* @notice Provides functions for managing legacy allocations.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IHorizonStakingExtension is IRewardsIssuer {
/**
* @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment
* An allocation is created in the allocate() function and closed in closeAllocation()
* @param indexer The indexer address
* @param subgraphDeploymentID The subgraph deployment ID
* @param tokens The amount of tokens allocated to the subgraph deployment
* @param createdAtEpoch The epoch when the allocation was created
* @param closedAtEpoch The epoch when the allocation was closed
* @param collectedFees The amount of collected fees for the allocation
* @param __DEPRECATED_effectiveAllocation Deprecated field.
* @param accRewardsPerAllocatedToken Snapshot used for reward calculation
* @param distributedRebates The amount of collected rebates that have been rebated
*/
struct Allocation {
address indexer;
bytes32 subgraphDeploymentID;
uint256 tokens;
uint256 createdAtEpoch;
uint256 closedAtEpoch;
uint256 collectedFees;
uint256 __DEPRECATED_effectiveAllocation;
uint256 accRewardsPerAllocatedToken;
uint256 distributedRebates;
}
/**
* @dev Possible states an allocation can be.
* States:
* - Null = indexer == address(0)
* - Active = not Null && tokens > 0
* - Closed = Active && closedAtEpoch != 0
*/
enum AllocationState {
Null,
Active,
Closed
}
/**
* @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`.
* An amount of `tokens` get unallocated from `subgraphDeploymentID`.
* This event also emits the POI (proof of indexing) submitted by the indexer.
* `isPublic` is true if the sender was someone other than the indexer.
* @param indexer The indexer address
* @param subgraphDeploymentID The subgraph deployment ID
* @param epoch The protocol epoch the allocation was closed on
* @param tokens The amount of tokens unallocated from the allocation
* @param allocationID The allocation identifier
* @param sender The address closing the allocation
* @param poi The proof of indexing submitted by the sender
* @param isPublic True if the allocation was force closed by someone other than the indexer/operator
*/
event AllocationClosed(
address indexed indexer,
bytes32 indexed subgraphDeploymentID,
uint256 epoch,
uint256 tokens,
address indexed allocationID,
address sender,
bytes32 poi,
bool isPublic
);
/**
* @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`.
* `epoch` is the protocol epoch the rebate was collected on
* The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees`
* is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt.
* `queryRebates` is the amount distributed to the `indexer` with `delegationFees` collected
* and sent to the delegation pool.
* @param assetHolder The address of the asset holder, the entity paying the query fees
* @param indexer The indexer address
* @param subgraphDeploymentID The subgraph deployment ID
* @param allocationID The allocation identifier
* @param epoch The protocol epoch the rebate was collected on
* @param tokens The amount of tokens collected
* @param protocolTax The amount of tokens burnt as protocol tax
* @param curationFees The amount of tokens distributed to the curation pool
* @param queryFees The amount of tokens collected as query fees
* @param queryRebates The amount of tokens distributed to the indexer
* @param delegationRewards The amount of tokens collected from the delegation pool
*/
event RebateCollected(
address assetHolder,
address indexed indexer,
bytes32 indexed subgraphDeploymentID,
address indexed allocationID,
uint256 epoch,
uint256 tokens,
uint256 protocolTax,
uint256 curationFees,
uint256 queryFees,
uint256 queryRebates,
uint256 delegationRewards
);
/**
* @dev Emitted when `indexer` was slashed for a total of `tokens` amount.
* Tracks `reward` amount of tokens given to `beneficiary`.
* @param indexer The indexer address
* @param tokens The amount of tokens slashed
* @param reward The amount of reward tokens to send to a beneficiary
* @param beneficiary The address of a beneficiary to receive a reward for the slashing
*/
event StakeSlashed(address indexed indexer, uint256 tokens, uint256 reward, address beneficiary);
/**
* @notice Close an allocation and free the staked tokens.
* To be eligible for rewards a proof of indexing must be presented.
* Presenting a bad proof is subject to slashable condition.
* To opt out of rewards set _poi to 0x0
* @param allocationID The allocation identifier
* @param poi Proof of indexing submitted for the allocated period
*/
function closeAllocation(address allocationID, bytes32 poi) external;
/**
* @dev Collect and rebate query fees to the indexer
* This function will accept calls with zero tokens.
* We use an exponential rebate formula to calculate the amount of tokens to rebate to the indexer.
* This implementation allows collecting multiple times on the same allocation, keeping track of the
* total amount rebated, the total amount collected and compensating the indexer for the difference.
* @param tokens Amount of tokens to collect
* @param allocationID Allocation where the tokens will be assigned
*/
function collect(uint256 tokens, address allocationID) external;
/**
* @notice Slash the indexer stake. Delegated tokens are not subject to slashing.
* Note that depending on the state of the indexer's stake, the slashed amount might be smaller than the
* requested slash amount. This can happen if the indexer has moved a significant part of their stake to
* a provision. Any outstanding slashing amount should be settled using Horizon's slash function
* {IHorizonStaking.slash}.
* @dev Can only be called by the slasher role.
* @param indexer Address of indexer to slash
* @param tokens Amount of tokens to slash from the indexer stake
* @param reward Amount of reward tokens to send to a beneficiary
* @param beneficiary Address of a beneficiary to receive a reward for the slashing
*/
function legacySlash(address indexer, uint256 tokens, uint256 reward, address beneficiary) external;
/**
* @notice (Legacy) Return true if operator is allowed for the service provider on the subgraph data service.
* @param operator Address of the operator
* @param indexer Address of the service provider
* @return True if operator is allowed for indexer, false otherwise
*/
function isOperator(address operator, address indexer) external view returns (bool);
/**
* @notice Getter that returns if an indexer has any stake.
* @param indexer Address of the indexer
* @return True if indexer has staked tokens
*/
function hasStake(address indexer) external view returns (bool);
/**
* @notice Get the total amount of tokens staked by the indexer.
* @param indexer Address of the indexer
* @return Amount of tokens staked by the indexer
*/
function getIndexerStakedTokens(address indexer) external view returns (uint256);
/**
* @notice Return the allocation by ID.
* @param allocationID Address used as allocation identifier
* @return Allocation data
*/
function getAllocation(address allocationID) external view returns (Allocation memory);
/**
* @notice Return the current state of an allocation
* @param allocationID Allocation identifier
* @return AllocationState enum with the state of the allocation
*/
function getAllocationState(address allocationID) external view returns (AllocationState);
/**
* @notice Return if allocationID is used.
* @param allocationID Address used as signer by the indexer for an allocation
* @return True if allocationID already used
*/
function isAllocation(address allocationID) external view returns (bool);
/**
* @notice Return the time in blocks to unstake
* Deprecated, now enforced by each data service (verifier)
* @return Thawing period in blocks
*/
function __DEPRECATED_getThawingPeriod() external view returns (uint64);
/**
* @notice Return the address of the subgraph data service.
* @dev TRANSITION PERIOD: After transition period move to main HorizonStaking contract
* @return Address of the subgraph data service
*/
function getSubgraphService() external view returns (address);
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IGraphPayments } from "../IGraphPayments.sol";
import { IHorizonStakingTypes } from "./IHorizonStakingTypes.sol";
/**
* @title Inferface for the {HorizonStaking} contract.
* @notice Provides functions for managing stake, provisions, delegations, and slashing.
* @dev Note that this interface only includes the functions implemented by {HorizonStaking} contract,
* and not those implemented by {HorizonStakingExtension}.
* Do not use this interface to interface with the {HorizonStaking} contract, use {IHorizonStaking} for
* the complete interface.
* @dev Most functions operate over {HorizonStaking} provisions. To uniquely identify a provision
* functions take `serviceProvider` and `verifier` addresses.
* @dev TRANSITION PERIOD: After transition period rename to IHorizonStaking.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IHorizonStakingMain {
// -- Events: stake --
/**
* @notice Emitted when a service provider unstakes tokens during the transition period.
* @param serviceProvider The address of the service provider
* @param tokens The amount of tokens now locked (including previously locked tokens)
* @param until The block number until the stake is locked
*/
event HorizonStakeLocked(address indexed serviceProvider, uint256 tokens, uint256 until);
/**
* @notice Emitted when a service provider withdraws tokens during the transition period.
* @param serviceProvider The address of the service provider
* @param tokens The amount of tokens withdrawn
*/
event HorizonStakeWithdrawn(address indexed serviceProvider, uint256 tokens);
// -- Events: provision --
/**
* @notice Emitted when a service provider provisions staked tokens to a verifier.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens provisioned
* @param maxVerifierCut The maximum cut, expressed in PPM of the slashed amount, that a verifier can take for themselves when slashing
* @param thawingPeriod The period in seconds that the tokens will be thawing before they can be removed from the provision
*/
event ProvisionCreated(
address indexed serviceProvider,
address indexed verifier,
uint256 tokens,
uint32 maxVerifierCut,
uint64 thawingPeriod
);
/**
* @notice Emitted whenever staked tokens are added to an existing provision
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens added to the provision
*/
event ProvisionIncreased(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a service provider thaws tokens from a provision.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens thawed
*/
event ProvisionThawed(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a service provider removes tokens from a provision.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens removed
*/
event TokensDeprovisioned(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a service provider stages a provision parameter update.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param maxVerifierCut The proposed maximum cut, expressed in PPM of the slashed amount, that a verifier can take for
* themselves when slashing
* @param thawingPeriod The proposed period in seconds that the tokens will be thawing before they can be removed from
* the provision
*/
event ProvisionParametersStaged(
address indexed serviceProvider,
address indexed verifier,
uint32 maxVerifierCut,
uint64 thawingPeriod
);
/**
* @notice Emitted when a service provider accepts a staged provision parameter update.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param maxVerifierCut The new maximum cut, expressed in PPM of the slashed amount, that a verifier can take for themselves
* when slashing
* @param thawingPeriod The new period in seconds that the tokens will be thawing before they can be removed from the provision
*/
event ProvisionParametersSet(
address indexed serviceProvider,
address indexed verifier,
uint32 maxVerifierCut,
uint64 thawingPeriod
);
/**
* @dev Emitted when an operator is allowed or denied by a service provider for a particular verifier
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param operator The address of the operator
* @param allowed Whether the operator is allowed or denied
*/
event OperatorSet(
address indexed serviceProvider,
address indexed verifier,
address indexed operator,
bool allowed
);
// -- Events: slashing --
/**
* @notice Emitted when a provision is slashed by a verifier.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens slashed (note this only represents service provider's slashed stake)
*/
event ProvisionSlashed(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a delegation pool is slashed by a verifier.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens slashed (note this only represents delegation pool's slashed stake)
*/
event DelegationSlashed(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a delegation pool would have been slashed by a verifier, but the slashing was skipped
* because delegation slashing global parameter is not enabled.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens that would have been slashed (note this only represents delegation pool's slashed stake)
*/
event DelegationSlashingSkipped(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when the verifier cut is sent to the verifier after slashing a provision.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param destination The address where the verifier cut is sent
* @param tokens The amount of tokens sent to the verifier
*/
event VerifierTokensSent(
address indexed serviceProvider,
address indexed verifier,
address indexed destination,
uint256 tokens
);
// -- Events: delegation --
/**
* @notice Emitted when tokens are delegated to a provision.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param delegator The address of the delegator
* @param tokens The amount of tokens delegated
* @param shares The amount of shares delegated
*/
event TokensDelegated(
address indexed serviceProvider,
address indexed verifier,
address indexed delegator,
uint256 tokens,
uint256 shares
);
/**
* @notice Emitted when a delegator undelegates tokens from a provision and starts
* thawing them.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param delegator The address of the delegator
* @param tokens The amount of tokens undelegated
* @param shares The amount of shares undelegated
*/
event TokensUndelegated(
address indexed serviceProvider,
address indexed verifier,
address indexed delegator,
uint256 tokens,
uint256 shares
);
/**
* @notice Emitted when a delegator withdraws tokens from a provision after thawing.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param delegator The address of the delegator
* @param tokens The amount of tokens withdrawn
*/
event DelegatedTokensWithdrawn(
address indexed serviceProvider,
address indexed verifier,
address indexed delegator,
uint256 tokens
);
/**
* @notice Emitted when `delegator` withdrew delegated `tokens` from `indexer` using `withdrawDelegated`.
* @dev This event is for the legacy `withdrawDelegated` function.
* @param indexer The address of the indexer
* @param delegator The address of the delegator
* @param tokens The amount of tokens withdrawn
*/
event StakeDelegatedWithdrawn(address indexed indexer, address indexed delegator, uint256 tokens);
/**
* @notice Emitted when tokens are added to a delegation pool's reserve.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param tokens The amount of tokens withdrawn
*/
event TokensToDelegationPoolAdded(address indexed serviceProvider, address indexed verifier, uint256 tokens);
/**
* @notice Emitted when a service provider sets delegation fee cuts for a verifier.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param paymentType The payment type for which the fee cut is set, as defined in {IGraphPayments}
* @param feeCut The fee cut set, in PPM
*/
event DelegationFeeCutSet(
address indexed serviceProvider,
address indexed verifier,
IGraphPayments.PaymentTypes indexed paymentType,
uint256 feeCut
);
// -- Events: thawing --
/**
* @notice Emitted when a thaw request is created.
* @dev Can be emitted by the service provider when thawing stake or by the delegator when undelegating.
* @param requestType The type of thaw request
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param owner The address of the owner of the thaw request.
* @param shares The amount of shares being thawed
* @param thawingUntil The timestamp until the stake is thawed
* @param thawRequestId The ID of the thaw request
* @param nonce The nonce of the thaw request
*/
event ThawRequestCreated(
IHorizonStakingTypes.ThawRequestType indexed requestType,
address indexed serviceProvider,
address indexed verifier,
address owner,
uint256 shares,
uint64 thawingUntil,
bytes32 thawRequestId,
uint256 nonce
);
/**
* @notice Emitted when a thaw request is fulfilled, meaning the stake is released.
* @param requestType The type of thaw request
* @param thawRequestId The ID of the thaw request
* @param tokens The amount of tokens being released
* @param shares The amount of shares being released
* @param thawingUntil The timestamp until the stake has thawed
* @param valid Whether the thaw request was valid at the time of fulfillment
*/
event ThawRequestFulfilled(
IHorizonStakingTypes.ThawRequestType indexed requestType,
bytes32 indexed thawRequestId,
uint256 tokens,
uint256 shares,
uint64 thawingUntil,
bool valid
);
/**
* @notice Emitted when a series of thaw requests are fulfilled.
* @param serviceProvider The address of the service provider
* @param verifier The address of the verifier
* @param owner The address of the owner of the thaw requests
* @param thawRequestsFulfilled The number of thaw requests fulfilled
* @param tokens The total amount of tokens being released
* @param requestType The type of thaw request
*/
event ThawRequestsFulfilled(
IHorizonStakingTypes.ThawRequestType indexed requestType,
address indexed serviceProvider,
address indexed verifier,
address owner,
uint256 thawRequestsFulfilled,
uint256 tokens
);
// -- Events: governance --
/**
* @notice Emitted when the global maximum thawing period allowed for provisions is set.
* @param maxThawingPeriod The new maximum thawing period
*/
event MaxThawingPeriodSet(uint64 maxThawingPeriod);
/**
* @notice Emitted when a verifier is allowed or disallowed to be used for locked provisions.
* @param verifier The address of the verifier
* @param allowed Whether the verifier is allowed or disallowed
*/
event AllowedLockedVerifierSet(address indexed verifier, bool allowed);
/**
* @notice Emitted when the legacy global thawing period is set to zero.
* @dev This marks the end of the transition period.
*/
event ThawingPeriodCleared();
/**
* @notice Emitted when the delegation slashing global flag is set.
*/
event DelegationSlashingEnabled();
// -- Errors: tokens
/**
* @notice Thrown when operating a zero token amount is not allowed.
*/
error HorizonStakingInvalidZeroTokens();
/**
* @notice Thrown when a minimum token amount is required to operate but it's not met.
* @param tokens The actual token amount
* @param minRequired The minimum required token amount
*/
error HorizonStakingInsufficientTokens(uint256 tokens, uint256 minRequired);
/**
* @notice Thrown when the amount of tokens exceeds the maximum allowed to operate.
* @param tokens The actual token amount
* @param maxTokens The maximum allowed token amount
*/
error HorizonStakingTooManyTokens(uint256 tokens, uint256 maxTokens);
// -- Errors: provision --
/**
* @notice Thrown when attempting to operate with a provision that does not exist.
* @param serviceProvider The service provider address
* @param verifier The verifier address
*/
error HorizonStakingInvalidProvision(address serviceProvider, address verifier);
/**
* @notice Thrown when the caller is not authorized to operate on a provision.
* @param caller The caller address
* @param serviceProvider The service provider address
* @param verifier The verifier address
*/
error HorizonStakingNotAuthorized(address serviceProvider, address verifier, address caller);
/**
* @notice Thrown when attempting to create a provision with a verifier other than the
* subgraph data service. This restriction only applies during the transition period.
* @param verifier The verifier address
*/
error HorizonStakingInvalidVerifier(address verifier);
/**
* @notice Thrown when attempting to create a provision with an invalid maximum verifier cut.
* @param maxVerifierCut The maximum verifier cut
*/
error HorizonStakingInvalidMaxVerifierCut(uint32 maxVerifierCut);
/**
* @notice Thrown when attempting to create a provision with an invalid thawing period.
* @param thawingPeriod The thawing period
* @param maxThawingPeriod The maximum `thawingPeriod` allowed
*/
error HorizonStakingInvalidThawingPeriod(uint64 thawingPeriod, uint64 maxThawingPeriod);
/**
* @notice Thrown when attempting to create a provision for a data service that already has a provision.
*/
error HorizonStakingProvisionAlreadyExists();
// -- Errors: stake --
/**
* @notice Thrown when the service provider has insufficient idle stake to operate.
* @param tokens The actual token amount
* @param minTokens The minimum required token amount
*/
error HorizonStakingInsufficientIdleStake(uint256 tokens, uint256 minTokens);
/**
* @notice Thrown during the transition period when the service provider has insufficient stake to
* cover their existing legacy allocations.
* @param tokens The actual token amount
* @param minTokens The minimum required token amount
*/
error HorizonStakingInsufficientStakeForLegacyAllocations(uint256 tokens, uint256 minTokens);
// -- Errors: delegation --
/**
* @notice Thrown when delegation shares obtained are below the expected amount.
* @param shares The actual share amount
* @param minShares The minimum required share amount
*/
error HorizonStakingSlippageProtection(uint256 shares, uint256 minShares);
/**
* @notice Thrown when operating a zero share amount is not allowed.
*/
error HorizonStakingInvalidZeroShares();
/**
* @notice Thrown when a minimum share amount is required to operate but it's not met.
* @param shares The actual share amount
* @param minShares The minimum required share amount
*/
error HorizonStakingInsufficientShares(uint256 shares, uint256 minShares);
/**
* @notice Thrown when as a result of slashing delegation pool has no tokens but has shares.
* @param serviceProvider The service provider address
* @param verifier The verifier address
*/
error HorizonStakingInvalidDelegationPoolState(address serviceProvider, address verifier);
/**
* @notice Thrown when attempting to operate with a delegation pool that does not exist.
* @param serviceProvider The service provider address
* @param verifier The verifier address
*/
error HorizonStakingInvalidDelegationPool(address serviceProvider, address verifier);
/**
* @notice Thrown when the minimum token amount required for delegation is not met.
* @param tokens The actual token amount
* @param minTokens The minimum required token amount
*/
error HorizonStakingInsufficientDelegationTokens(uint256 tokens, uint256 minTokens);
/**
* @notice Thrown when attempting to redelegate with a serivce provider that is the zero address.
*/
error HorizonStakingInvalidServiceProviderZeroAddress();
/**
* @notice Thrown when attempting to redelegate with a verifier that is the zero address.
*/
error HorizonStakingInvalidVerifierZeroAddress();
// -- Errors: thaw requests --
/**
* @notice Thrown when attempting to fulfill a thaw request but there is nothing thawing.
*/
error HorizonStakingNothingThawing();
/**
* @notice Thrown when a service provider has too many thaw requests.
*/
error HorizonStakingTooManyThawRequests();
/**
* @notice Thrown when attempting to withdraw tokens that have not thawed (legacy undelegate).
*/
error HorizonStakingNothingToWithdraw();
// -- Errors: misc --
/**
* @notice Thrown during the transition period when attempting to withdraw tokens that are still thawing.
* @dev Note this thawing refers to the global thawing period applied to legacy allocated tokens,
* it does not refer to thaw requests.
* @param until The block number until the stake is locked
*/
error HorizonStakingStillThawing(uint256 until);
/**
* @notice Thrown when a service provider attempts to operate on verifiers that are not allowed.
* @dev Only applies to stake from locked wallets.
* @param verifier The verifier address
*/
error HorizonStakingVerifierNotAllowed(address verifier);
/**
* @notice Thrown when a service provider attempts to change their own operator access.
*/
error HorizonStakingCallerIsServiceProvider();
/**
* @notice Thrown when trying to set a delegation fee cut that is not valid.
* @param feeCut The fee cut
*/
error HorizonStakingInvalidDelegationFeeCut(uint256 feeCut);
/**
* @notice Thrown when a legacy slash fails.
*/
error HorizonStakingLegacySlashFailed();
/**
* @notice Thrown when there attempting to slash a provision with no tokens to slash.
*/
error HorizonStakingNoTokensToSlash();
// -- Functions --
/**
* @notice Deposit tokens on the staking contract.
* @dev Pulls tokens from the caller.
*
* Requirements:
* - `_tokens` cannot be zero.
* - Caller must have previously approved this contract to pull tokens from their balance.
*
* Emits a {HorizonStakeDeposited} event.
*
* @param tokens Amount of tokens to stake
*/
function stake(uint256 tokens) external;
/**
* @notice Deposit tokens on the service provider stake, on behalf of the service provider.
* @dev Pulls tokens from the caller.
*
* Requirements:
* - `_tokens` cannot be zero.
* - Caller must have previously approved this contract to pull tokens from their balance.
*
* Emits a {HorizonStakeDeposited} event.
*
* @param serviceProvider Address of the service provider
* @param tokens Amount of tokens to stake
*/
function stakeTo(address serviceProvider, uint256 tokens) external;
/**
* @notice Deposit tokens on the service provider stake, on behalf of the service provider,
* provisioned to a specific verifier.
* @dev This function can be called by the service provider, by an authorized operator or by the verifier itself.
* @dev Requirements:
* - The `serviceProvider` must have previously provisioned stake to `verifier`.
* - `_tokens` cannot be zero.
* - Caller must have previously approved this contract to pull tokens from their balance.
*
* Emits {HorizonStakeDeposited} and {ProvisionIncreased} events.
*
* @param serviceProvider Address of the service provider
* @param verifier Address of the verifier
* @param tokens Amount of tokens to stake
*/
function stakeToProvision(address serviceProvider, address verifier, uint256 tokens) external;
/**
* @notice Move idle stake back to the owner's account.
* Stake is removed from the protocol:
* - During the transition period it's locked for a period of time before it can be withdrawn
* by calling {withdraw}.
* - After the transition period it's immediately withdrawn.
* Note that after the transition period if there are tokens still locked they will have to be
* withdrawn by calling {withdraw}.
* @dev Requirements:
* - `_tokens` cannot be zero.
* - `_serviceProvider` must have enough idle stake to cover the staking amount and any
* legacy allocation.
*
* Emits a {HorizonStakeLocked} event during the transition period.
* Emits a {HorizonStakeWithdrawn} event after the transition period.
*
* @param tokens Amount of tokens to unstake
*/
function unstake(uint256 tokens) external;
/**
* @notice Withdraw service provider tokens once the thawing period (initiated by {unstake}) has passed.
* All thawed tokens are withdrawn.
* @dev This is only needed during the transition period while we still have
* a global lock. After that, unstake() will automatically withdraw.
*/
function withdraw() external;
/**
* @notice Provision stake to a verifier. The tokens will be locked with a thawing period
* and will be slashable by the verifier. This is the main mechanism to provision stake to a data
* service, where the data service is the verifier.
* This function can be called by the service provider or by an operator authorized by the provider
* for this specific verifier.
* @dev During the transition period, only the subgraph data service can be used as a verifier. This
* prevents an escape hatch for legacy allocation stake.
* @dev Requirements:
* - `tokens` cannot be zero.
* - The `serviceProvider` must have enough idle stake to cover the tokens to provision.
* - `maxVerifierCut` must be a valid PPM.
* - `thawingPeriod` must be less than or equal to `_maxThawingPeriod`.
*
* Emits a {ProvisionCreated} event.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address for which the tokens are provisioned (who will be able to slash the tokens)
* @param tokens The amount of tokens that will be locked and slashable
* @param maxVerifierCut The maximum cut, expressed in PPM, that a verifier can transfer instead of burning when slashing
* @param thawingPeriod The period in seconds that the tokens will be thawing before they can be removed from the provision
*/
function provision(
address serviceProvider,
address verifier,
uint256 tokens,
uint32 maxVerifierCut,
uint64 thawingPeriod
) external;
/**
* @notice Adds tokens from the service provider's idle stake to a provision
* @dev
*
* Requirements:
* - The `serviceProvider` must have previously provisioned stake to `verifier`.
* - `tokens` cannot be zero.
* - The `serviceProvider` must have enough idle stake to cover the tokens to add.
*
* Emits a {ProvisionIncreased} event.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param tokens The amount of tokens to add to the provision
*/
function addToProvision(address serviceProvider, address verifier, uint256 tokens) external;
/**
* @notice Start thawing tokens to remove them from a provision.
* This function can be called by the service provider or by an operator authorized by the provider
* for this specific verifier.
*
* Note that removing tokens from a provision is a two step process:
* - First the tokens are thawed using this function.
* - Then after the thawing period, the tokens are removed from the provision using {deprovision}
* or {reprovision}.
*
* @dev Requirements:
* - The provision must have enough tokens available to thaw.
* - `tokens` cannot be zero.
*
* Emits {ProvisionThawed} and {ThawRequestCreated} events.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address for which the tokens are provisioned
* @param tokens The amount of tokens to thaw
* @return The ID of the thaw request
*/
function thaw(address serviceProvider, address verifier, uint256 tokens) external returns (bytes32);
/**
* @notice Remove tokens from a provision and move them back to the service provider's idle stake.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error. Otherwise, the function
* will attempt to fulfill all thaw requests until the first one that is not yet expired is found.
*
* Requirements:
* - Must have previously initiated a thaw request using {thaw}.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {TokensDeprovisioned} events.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function deprovision(address serviceProvider, address verifier, uint256 nThawRequests) external;
/**
* @notice Move already thawed stake from one provision into another provision
* This function can be called by the service provider or by an operator authorized by the provider
* for the two corresponding verifiers.
* @dev Requirements:
* - Must have previously initiated a thaw request using {thaw}.
* - `tokens` cannot be zero.
* - The `serviceProvider` must have previously provisioned stake to `newVerifier`.
* - The `serviceProvider` must have enough idle stake to cover the tokens to add.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled}, {TokensDeprovisioned} and {ProvisionIncreased}
* events.
*
* @param serviceProvider The service provider address
* @param oldVerifier The verifier address for which the tokens are currently provisioned
* @param newVerifier The verifier address for which the tokens will be provisioned
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function reprovision(
address serviceProvider,
address oldVerifier,
address newVerifier,
uint256 nThawRequests
) external;
/**
* @notice Stages a provision parameter update. Note that the change is not effective until the verifier calls
* {acceptProvisionParameters}. Calling this function is a no-op if the new parameters are the same as the current
* ones.
* @dev This two step update process prevents the service provider from changing the parameters
* without the verifier's consent.
*
* Requirements:
* - `thawingPeriod` must be less than or equal to `_maxThawingPeriod`. Note that if `_maxThawingPeriod` changes the
* function will not revert if called with the same thawing period as the current one.
*
* Emits a {ProvisionParametersStaged} event if at least one of the parameters changed.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param maxVerifierCut The proposed maximum cut, expressed in PPM of the slashed amount, that a verifier can take for
* themselves when slashing
* @param thawingPeriod The proposed period in seconds that the tokens will be thawing before they can be removed from
* the provision
*/
function setProvisionParameters(
address serviceProvider,
address verifier,
uint32 maxVerifierCut,
uint64 thawingPeriod
) external;
/**
* @notice Accepts a staged provision parameter update.
* @dev Only the provision's verifier can call this function.
*
* Emits a {ProvisionParametersSet} event.
*
* @param serviceProvider The service provider address
*/
function acceptProvisionParameters(address serviceProvider) external;
/**
* @notice Delegate tokens to a provision.
* @dev Requirements:
* - `tokens` cannot be zero.
* - Caller must have previously approved this contract to pull tokens from their balance.
* - The provision must exist.
*
* Emits a {TokensDelegated} event.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param tokens The amount of tokens to delegate
* @param minSharesOut The minimum amount of shares to accept, slippage protection.
*/
function delegate(address serviceProvider, address verifier, uint256 tokens, uint256 minSharesOut) external;
/**
* @notice Add tokens to a delegation pool without issuing shares.
* Used by data services to pay delegation fees/rewards.
* Delegators SHOULD NOT call this function.
*
* @dev Requirements:
* - `tokens` cannot be zero.
* - Caller must have previously approved this contract to pull tokens from their balance.
*
* Emits a {TokensToDelegationPoolAdded} event.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address for which the tokens are provisioned
* @param tokens The amount of tokens to add to the delegation pool
*/
function addToDelegationPool(address serviceProvider, address verifier, uint256 tokens) external;
/**
* @notice Undelegate tokens from a provision and start thawing them.
* Note that undelegating tokens from a provision is a two step process:
* - First the tokens are thawed using this function.
* - Then after the thawing period, the tokens are removed from the provision using {withdrawDelegated}.
*
* Requirements:
* - `shares` cannot be zero.
*
* Emits a {TokensUndelegated} and {ThawRequestCreated} event.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param shares The amount of shares to undelegate
* @return The ID of the thaw request
*/
function undelegate(address serviceProvider, address verifier, uint256 shares) external returns (bytes32);
/**
* @notice Withdraw undelegated tokens from a provision after thawing.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error. Otherwise, the function
* will attempt to fulfill all thaw requests until the first one that is not yet expired is found.
* @dev If the delegation pool was completely slashed before withdrawing, calling this function will fulfill
* the thaw requests with an amount equal to zero.
*
* Requirements:
* - Must have previously initiated a thaw request using {undelegate}.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function withdrawDelegated(address serviceProvider, address verifier, uint256 nThawRequests) external;
/**
* @notice Re-delegate undelegated tokens from a provision after thawing to a `newServiceProvider` and `newVerifier`.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error.
*
* Requirements:
* - Must have previously initiated a thaw request using {undelegate}.
* - `newServiceProvider` and `newVerifier` must not be the zero address.
* - `newServiceProvider` must have previously provisioned stake to `newVerifier`.
*
* Emits {ThawRequestFulfilled}, {ThawRequestsFulfilled} and {DelegatedTokensWithdrawn} events.
*
* @param oldServiceProvider The old service provider address
* @param oldVerifier The old verifier address
* @param newServiceProvider The address of a new service provider
* @param newVerifier The address of a new verifier
* @param minSharesForNewProvider The minimum amount of shares to accept for the new service provider
* @param nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function redelegate(
address oldServiceProvider,
address oldVerifier,
address newServiceProvider,
address newVerifier,
uint256 minSharesForNewProvider,
uint256 nThawRequests
) external;
/**
* @notice Set the fee cut for a verifier on a specific payment type.
* @dev Emits a {DelegationFeeCutSet} event.
* @param serviceProvider The service provider address
* @param verifier The verifier address
* @param paymentType The payment type for which the fee cut is set, as defined in {IGraphPayments}
* @param feeCut The fee cut to set, in PPM
*/
function setDelegationFeeCut(
address serviceProvider,
address verifier,
IGraphPayments.PaymentTypes paymentType,
uint256 feeCut
) external;
/**
* @notice Delegate tokens to the subgraph data service provision.
* This function is for backwards compatibility with the legacy staking contract.
* It only allows delegating to the subgraph data service and DOES NOT have slippage protection.
* @dev See {delegate}.
* @param serviceProvider The service provider address
* @param tokens The amount of tokens to delegate
*/
function delegate(address serviceProvider, uint256 tokens) external;
/**
* @notice Undelegate tokens from the subgraph data service provision and start thawing them.
* This function is for backwards compatibility with the legacy staking contract.
* It only allows undelegating from the subgraph data service.
* @dev See {undelegate}.
* @param serviceProvider The service provider address
* @param shares The amount of shares to undelegate
*/
function undelegate(address serviceProvider, uint256 shares) external;
/**
* @notice Withdraw undelegated tokens from the subgraph data service provision after thawing.
* This function is for backwards compatibility with the legacy staking contract.
* It only allows withdrawing tokens undelegated before horizon upgrade.
* @dev See {delegate}.
* @param serviceProvider The service provider address
* @param deprecated Deprecated parameter kept for backwards compatibility
* @return The amount of tokens withdrawn
*/
function withdrawDelegated(
address serviceProvider,
address deprecated // kept for backwards compatibility
) external returns (uint256);
/**
* @notice Slash a service provider. This can only be called by a verifier to which
* the provider has provisioned stake, and up to the amount of tokens they have provisioned.
* If the service provider's stake is not enough, the associated delegation pool might be slashed
* depending on the value of the global delegation slashing flag.
*
* Part of the slashed tokens are sent to the `verifierDestination` as a reward.
*
* @dev Requirements:
* - `tokens` must be less than or equal to the amount of tokens provisioned by the service provider.
* - `tokensVerifier` must be less than the provision's tokens times the provision's maximum verifier cut.
*
* Emits a {ProvisionSlashed} and {VerifierTokensSent} events.
* Emits a {DelegationSlashed} or {DelegationSlashingSkipped} event depending on the global delegation slashing
* flag.
*
* @param serviceProvider The service provider to slash
* @param tokens The amount of tokens to slash
* @param tokensVerifier The amount of tokens to transfer instead of burning
* @param verifierDestination The address to transfer the verifier cut to
*/
function slash(
address serviceProvider,
uint256 tokens,
uint256 tokensVerifier,
address verifierDestination
) external;
/**
* @notice Provision stake to a verifier using locked tokens (i.e. from GraphTokenLockWallets).
* @dev See {provision}.
*
* Additional requirements:
* - The `verifier` must be allowed to be used for locked provisions.
*
* @param serviceProvider The service provider address
* @param verifier The verifier address for which the tokens are provisioned (who will be able to slash the tokens)
* @param tokens The amount of tokens that will be locked and slashable
* @param maxVerifierCut The maximum cut, expressed in PPM, that a verifier can transfer instead of burning when slashing
* @param thawingPeriod The period in seconds that the tokens will be thawing before they can be removed from the provision
*/
function provisionLocked(
address serviceProvider,
address verifier,
uint256 tokens,
uint32 maxVerifierCut,
uint64 thawingPeriod
) external;
/**
* @notice Authorize or unauthorize an address to be an operator for the caller on a verifier.
*
* @dev See {setOperator}.
* Additional requirements:
* - The `verifier` must be allowed to be used for locked provisions.
*
* @param verifier The verifier / data service on which they'll be allowed to operate
* @param operator Address to authorize or unauthorize
* @param allowed Whether the operator is authorized or not
*/
function setOperatorLocked(address verifier, address operator, bool allowed) external;
/**
* @notice Sets a verifier as a globally allowed verifier for locked provisions.
* @dev This function can only be called by the contract governor, it's used to maintain
* a whitelist of verifiers that do not allow the stake from a locked wallet to escape the lock.
* @dev Emits a {AllowedLockedVerifierSet} event.
* @param verifier The verifier address
* @param allowed Whether the verifier is allowed or not
*/
function setAllowedLockedVerifier(address verifier, bool allowed) external;
/**
* @notice Set the global delegation slashing flag to true.
* @dev This function can only be called by the contract governor.
*/
function setDelegationSlashingEnabled() external;
/**
* @notice Clear the legacy global thawing period.
* This signifies the end of the transition period, after which no legacy allocations should be left.
* @dev This function can only be called by the contract governor.
* @dev Emits a {ThawingPeriodCleared} event.
*/
function clearThawingPeriod() external;
/**
* @notice Sets the global maximum thawing period allowed for provisions.
* @param maxThawingPeriod The new maximum thawing period, in seconds
*/
function setMaxThawingPeriod(uint64 maxThawingPeriod) external;
/**
* @notice Authorize or unauthorize an address to be an operator for the caller on a data service.
* @dev Emits a {OperatorSet} event.
* @param verifier The verifier / data service on which they'll be allowed to operate
* @param operator Address to authorize or unauthorize
* @param allowed Whether the operator is authorized or not
*/
function setOperator(address verifier, address operator, bool allowed) external;
/**
* @notice Check if an operator is authorized for the caller on a specific verifier / data service.
* @param serviceProvider The service provider on behalf of whom they're claiming to act
* @param verifier The verifier / data service on which they're claiming to act
* @param operator The address to check for auth
* @return Whether the operator is authorized or not
*/
function isAuthorized(address serviceProvider, address verifier, address operator) external view returns (bool);
/**
* @notice Get the address of the staking extension.
* @return The address of the staking extension
*/
function getStakingExtension() external view returns (address);
}// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.27; /** * @title Defines the data types used in the Horizon staking contract * @dev In order to preserve storage compatibility some data structures keep deprecated fields. * These structures have then two representations, an internal one used by the contract storage and a public one. * Getter functions should retrieve internal representations, remove deprecated fields and return the public representation. * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ interface IHorizonStakingTypes { /** * @notice Represents stake assigned to a specific verifier/data service. * Provisioned stake is locked and can be used as economic security by a data service. * @param tokens Service provider tokens in the provision (does not include delegated tokens) * @param tokensThawing Service provider tokens that are being thawed (and will stop being slashable soon) * @param sharesThawing Shares representing the thawing tokens * @param maxVerifierCut Max amount that can be taken by the verifier when slashing, expressed in parts-per-million of the amount slashed * @param thawingPeriod Time, in seconds, tokens must thaw before being withdrawn * @param createdAt Timestamp when the provision was created * @param maxVerifierCutPending Pending value for `maxVerifierCut`. Verifier needs to accept it before it becomes active. * @param thawingPeriodPending Pending value for `thawingPeriod`. Verifier needs to accept it before it becomes active. * @param lastParametersStagedAt Timestamp when the provision parameters were last staged. Can be used by data service implementation to * implement arbitrary parameter update logic. * @param thawingNonce Value of the current thawing nonce. Thaw requests with older nonces are invalid. */ struct Provision { uint256 tokens; uint256 tokensThawing; uint256 sharesThawing; uint32 maxVerifierCut; uint64 thawingPeriod; uint64 createdAt; uint32 maxVerifierCutPending; uint64 thawingPeriodPending; uint256 lastParametersStagedAt; uint256 thawingNonce; } /** * @notice Public representation of a service provider. * @dev See {ServiceProviderInternal} for the actual storage representation * @param tokensStaked Total amount of tokens on the provider stake (only staked by the provider, includes all provisions) * @param tokensProvisioned Total amount of tokens locked in provisions (only staked by the provider) */ struct ServiceProvider { uint256 tokensStaked; uint256 tokensProvisioned; } /** * @notice Internal representation of a service provider. * @dev It contains deprecated fields from the `Indexer` struct to maintain storage compatibility. * @param tokensStaked Total amount of tokens on the provider stake (only staked by the provider, includes all provisions) * @param __DEPRECATED_tokensAllocated (Deprecated) Tokens used in allocations * @param __DEPRECATED_tokensLocked (Deprecated) Tokens locked for withdrawal subject to thawing period * @param __DEPRECATED_tokensLockedUntil (Deprecated) Block when locked tokens can be withdrawn * @param tokensProvisioned Total amount of tokens locked in provisions (only staked by the provider) */ struct ServiceProviderInternal { uint256 tokensStaked; uint256 __DEPRECATED_tokensAllocated; uint256 __DEPRECATED_tokensLocked; uint256 __DEPRECATED_tokensLockedUntil; uint256 tokensProvisioned; } /** * @notice Public representation of a delegation pool. * @dev See {DelegationPoolInternal} for the actual storage representation * @param tokens Total tokens as pool reserves * @param shares Total shares minted in the pool * @param tokensThawing Tokens thawing in the pool * @param sharesThawing Shares representing the thawing tokens * @param thawingNonce Value of the current thawing nonce. Thaw requests with older nonces are invalid. */ struct DelegationPool { uint256 tokens; uint256 shares; uint256 tokensThawing; uint256 sharesThawing; uint256 thawingNonce; } /** * @notice Internal representation of a delegation pool. * @dev It contains deprecated fields from the previous version of the `DelegationPool` struct * to maintain storage compatibility. * @param __DEPRECATED_cooldownBlocks (Deprecated) Time, in blocks, an indexer must wait before updating delegation parameters * @param __DEPRECATED_indexingRewardCut (Deprecated) Percentage of indexing rewards for the service provider, in PPM * @param __DEPRECATED_queryFeeCut (Deprecated) Percentage of query fees for the service provider, in PPM * @param __DEPRECATED_updatedAtBlock (Deprecated) Block when the delegation parameters were last updated * @param tokens Total tokens as pool reserves * @param shares Total shares minted in the pool * @param delegators Delegation details by delegator * @param tokensThawing Tokens thawing in the pool * @param sharesThawing Shares representing the thawing tokens * @param thawingNonce Value of the current thawing nonce. Thaw requests with older nonces are invalid. */ struct DelegationPoolInternal { uint32 __DEPRECATED_cooldownBlocks; uint32 __DEPRECATED_indexingRewardCut; uint32 __DEPRECATED_queryFeeCut; uint256 __DEPRECATED_updatedAtBlock; uint256 tokens; uint256 shares; mapping(address delegator => DelegationInternal delegation) delegators; uint256 tokensThawing; uint256 sharesThawing; uint256 thawingNonce; } /** * @notice Public representation of delegation details. * @dev See {DelegationInternal} for the actual storage representation * @param shares Shares owned by a delegator in the pool */ struct Delegation { uint256 shares; } /** * @notice Internal representation of delegation details. * @dev It contains deprecated fields from the previous version of the `Delegation` struct * to maintain storage compatibility. * @param shares Shares owned by the delegator in the pool * @param __DEPRECATED_tokensLocked Tokens locked for undelegation * @param __DEPRECATED_tokensLockedUntil Epoch when locked tokens can be withdrawn */ struct DelegationInternal { uint256 shares; uint256 __DEPRECATED_tokensLocked; uint256 __DEPRECATED_tokensLockedUntil; } /** * @dev Enum to specify the type of thaw request. * @param Provision Represents a thaw request for a provision. * @param Delegation Represents a thaw request for a delegation. */ enum ThawRequestType { Provision, Delegation } /** * @notice Details of a stake thawing operation. * @dev ThawRequests are stored in linked lists by service provider/delegator, * ordered by creation timestamp. * @param shares Shares that represent the tokens being thawed * @param thawingUntil The timestamp when the thawed funds can be removed from the provision * @param nextRequest Id of the next thaw request in the linked list * @param thawingNonce Used to invalidate unfulfilled thaw requests */ struct ThawRequest { uint256 shares; uint64 thawingUntil; bytes32 nextRequest; uint256 thawingNonce; } /** * @notice Parameters to fulfill thaw requests. * @dev This struct is used to avoid stack too deep error in the `fulfillThawRequests` function. * @param requestType The type of thaw request (Provision or Delegation) * @param serviceProvider The address of the service provider * @param verifier The address of the verifier * @param owner The address of the owner of the thaw request * @param tokensThawing The current amount of tokens already thawing * @param sharesThawing The current amount of shares already thawing * @param nThawRequests The number of thaw requests to fulfill. If set to 0, all thaw requests are fulfilled. * @param thawingNonce The current valid thawing nonce. Any thaw request with a different nonce is invalid and should be ignored. */ struct FulfillThawRequestsParams { ThawRequestType requestType; address serviceProvider; address verifier; address owner; uint256 tokensThawing; uint256 sharesThawing; uint256 nThawRequests; uint256 thawingNonce; } /** * @notice Results of the traversal of thaw requests. * @dev This struct is used to avoid stack too deep error in the `fulfillThawRequests` function. * @param requestsFulfilled The number of thaw requests fulfilled * @param tokensThawed The total amount of tokens thawed * @param tokensThawing The total amount of tokens thawing * @param sharesThawing The total amount of shares thawing */ struct TraverseThawRequestsResults { uint256 requestsFulfilled; uint256 tokensThawed; uint256 tokensThawing; uint256 sharesThawing; } }
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
/**
* @title Interface for the {LinkedList} library contract.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface ILinkedList {
/**
* @notice Represents a linked list
* @param head The head of the list
* @param tail The tail of the list
* @param nonce A nonce, which can optionally be used to generate unique ids
* @param count The number of items in the list
*/
struct List {
bytes32 head;
bytes32 tail;
uint256 nonce;
uint256 count;
}
/**
* @notice Thrown when trying to remove an item from an empty list
*/
error LinkedListEmptyList();
/**
* @notice Thrown when trying to add an item to a list that has reached the maximum number of elements
*/
error LinkedListMaxElementsExceeded();
/**
* @notice Thrown when trying to traverse a list with more iterations than elements
*/
error LinkedListInvalidIterations();
/**
* @notice Thrown when trying to add an item with id equal to bytes32(0)
*/
error LinkedListInvalidZeroId();
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IGraphPayments } from "./IGraphPayments.sol";
/**
* @title Interface for a payments collector contract as defined by Graph Horizon payments protocol
* @notice Contracts implementing this interface can be used with the payments protocol. First, a payer must
* approve the collector to collect payments on their behalf. Only then can payment collection be initiated
* using the collector contract.
*
* @dev It's important to note that it's the collector contract's responsibility to validate the payment
* request is legitimate.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IPaymentsCollector {
/**
* @notice Emitted when a payment is collected
* @param paymentType The payment type collected as defined by {IGraphPayments}
* @param collectionId The id for the collection. Can be used at the discretion of the collector to group multiple payments.
* @param payer The address of the payer
* @param receiver The address of the receiver
* @param dataService The address of the data service
* @param tokens The amount of tokens being collected
*/
event PaymentCollected(
IGraphPayments.PaymentTypes paymentType,
bytes32 indexed collectionId,
address indexed payer,
address receiver,
address indexed dataService,
uint256 tokens
);
/**
* @notice Initiate a payment collection through the payments protocol
* @dev This function should require the caller to present some form of evidence of the payer's debt to
* the receiver. The collector should validate this evidence and, if valid, collect the payment.
*
* Emits a {PaymentCollected} event
*
* @param paymentType The payment type to collect, as defined by {IGraphPayments}
* @param data Additional data required for the payment collection. Will vary depending on the collector
* implementation.
* @return The amount of tokens collected
*/
function collect(IGraphPayments.PaymentTypes paymentType, bytes memory data) external returns (uint256);
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IGraphPayments } from "./IGraphPayments.sol";
/**
* @title Interface for the {PaymentsEscrow} contract
* @notice This contract is part of the Graph Horizon payments protocol. It holds the funds (GRT)
* for payments made through the payments protocol for services provided
* via a Graph Horizon data service.
*
* Payers deposit funds on the escrow, signalling their ability to pay for a service, and only
* being able to retrieve them after a thawing period. Receivers collect funds from the escrow,
* provided the payer has authorized them. The payer authorization is delegated to a payment
* collector contract which implements the {IPaymentsCollector} interface.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
interface IPaymentsEscrow {
/**
* @notice Escrow account for a payer-collector-receiver tuple
* @param balance The total token balance for the payer-collector-receiver tuple
* @param tokensThawing The amount of tokens currently being thawed
* @param thawEndTimestamp The timestamp at which thawing period ends (zero if not thawing)
*/
struct EscrowAccount {
uint256 balance;
uint256 tokensThawing;
uint256 thawEndTimestamp;
}
/**
* @notice Emitted when a payer deposits funds into the escrow for a payer-collector-receiver tuple
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens deposited
*/
event Deposit(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
/**
* @notice Emitted when a payer cancels an escrow thawing
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokensThawing The amount of tokens that were being thawed
* @param thawEndTimestamp The timestamp at which the thawing period was ending
*/
event CancelThaw(
address indexed payer,
address indexed collector,
address indexed receiver,
uint256 tokensThawing,
uint256 thawEndTimestamp
);
/**
* @notice Emitted when a payer thaws funds from the escrow for a payer-collector-receiver tuple
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens being thawed
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
event Thaw(
address indexed payer,
address indexed collector,
address indexed receiver,
uint256 tokens,
uint256 thawEndTimestamp
);
/**
* @notice Emitted when a payer withdraws funds from the escrow for a payer-collector-receiver tuple
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens withdrawn
*/
event Withdraw(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens);
/**
* @notice Emitted when a collector collects funds from the escrow for a payer-collector-receiver tuple
* @param paymentType The type of payment being collected as defined in the {IGraphPayments} interface
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens collected
* @param receiverDestination The address where the receiver's payment should be sent.
*/
event EscrowCollected(
IGraphPayments.PaymentTypes indexed paymentType,
address indexed payer,
address indexed collector,
address receiver,
uint256 tokens,
address receiverDestination
);
// -- Errors --
/**
* @notice Thrown when a protected function is called and the contract is paused.
*/
error PaymentsEscrowIsPaused();
/**
* @notice Thrown when the available balance is insufficient to perform an operation
* @param balance The current balance
* @param minBalance The minimum required balance
*/
error PaymentsEscrowInsufficientBalance(uint256 balance, uint256 minBalance);
/**
* @notice Thrown when a thawing is expected to be in progress but it is not
*/
error PaymentsEscrowNotThawing();
/**
* @notice Thrown when a thawing is still in progress
* @param currentTimestamp The current timestamp
* @param thawEndTimestamp The timestamp at which the thawing period ends
*/
error PaymentsEscrowStillThawing(uint256 currentTimestamp, uint256 thawEndTimestamp);
/**
* @notice Thrown when setting the thawing period to a value greater than the maximum
* @param thawingPeriod The thawing period
* @param maxWaitPeriod The maximum wait period
*/
error PaymentsEscrowThawingPeriodTooLong(uint256 thawingPeriod, uint256 maxWaitPeriod);
/**
* @notice Thrown when the contract balance is not consistent with the collection amount
* @param balanceBefore The balance before the collection
* @param balanceAfter The balance after the collection
* @param tokens The amount of tokens collected
*/
error PaymentsEscrowInconsistentCollection(uint256 balanceBefore, uint256 balanceAfter, uint256 tokens);
/**
* @notice Thrown when operating a zero token amount is not allowed.
*/
error PaymentsEscrowInvalidZeroTokens();
/**
* @notice The maximum thawing period for escrow funds withdrawal
* @return The maximum thawing period in seconds
*/
function MAX_WAIT_PERIOD() external view returns (uint256);
/**
* @notice The thawing period for escrow funds withdrawal
* @return The thawing period in seconds
*/
function WITHDRAW_ESCROW_THAWING_PERIOD() external view returns (uint256);
/**
* @notice Initialize the contract
*/
function initialize() external;
/**
* @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where
* the payer is the transaction caller.
* @dev Emits a {Deposit} event
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens to deposit
*/
function deposit(address collector, address receiver, uint256 tokens) external;
/**
* @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where
* the payer can be specified.
* @dev Emits a {Deposit} event
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens to deposit
*/
function depositTo(address payer, address collector, address receiver, uint256 tokens) external;
/**
* @notice Thaw a specific amount of escrow from a payer-collector-receiver's escrow account.
* The payer is the transaction caller.
* Note that repeated calls to this function will overwrite the previous thawing amount
* and reset the thawing period.
* @dev Requirements:
* - `tokens` must be less than or equal to the available balance
*
* Emits a {Thaw} event.
*
* @param collector The address of the collector
* @param receiver The address of the receiver
* @param tokens The amount of tokens to thaw
*/
function thaw(address collector, address receiver, uint256 tokens) external;
/**
* @notice Cancels the thawing of escrow from a payer-collector-receiver's escrow account.
* @param collector The address of the collector
* @param receiver The address of the receiver
* @dev Requirements:
* - The payer must be thawing funds
* Emits a {CancelThaw} event.
*/
function cancelThaw(address collector, address receiver) external;
/**
* @notice Withdraws all thawed escrow from a payer-collector-receiver's escrow account.
* The payer is the transaction caller.
* Note that the withdrawn funds might be less than the thawed amount if there were
* payment collections in the meantime.
* @dev Requirements:
* - Funds must be thawed
*
* Emits a {Withdraw} event
*
* @param collector The address of the collector
* @param receiver The address of the receiver
*/
function withdraw(address collector, address receiver) external;
/**
* @notice Collects funds from the payer-collector-receiver's escrow and sends them to {GraphPayments} for
* distribution using the Graph Horizon Payments protocol.
* The function will revert if there are not enough funds in the escrow.
*
* Emits an {EscrowCollected} event
*
* @param paymentType The type of payment being collected as defined in the {IGraphPayments} interface
* @param payer The address of the payer
* @param receiver The address of the receiver
* @param tokens The amount of tokens to collect
* @param dataService The address of the data service
* @param dataServiceCut The data service cut in PPM that {GraphPayments} should send
* @param receiverDestination The address where the receiver's payment should be sent.
*/
function collect(
IGraphPayments.PaymentTypes paymentType,
address payer,
address receiver,
uint256 tokens,
address dataService,
uint256 dataServiceCut,
address receiverDestination
) external;
/**
* @notice Get the balance of a payer-collector-receiver tuple
* This function will return 0 if the current balance is less than the amount of funds being thawed.
* @param payer The address of the payer
* @param collector The address of the collector
* @param receiver The address of the receiver
* @return The balance of the payer-collector-receiver tuple
*/
function getBalance(address payer, address collector, address receiver) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Multicall.sol)
pragma solidity ^0.8.20;
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ContextUpgradeable} from "./ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* Consider any assumption about calldata validation performed by the sender may be violated if it's not especially
* careful about sending transactions invoking {multicall}. For example, a relay address that filters function
* selectors won't filter calls nested within a {multicall} operation.
*
* NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {Context-_msgSender}).
* If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data`
* to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of
* {Context-_msgSender} are not propagated to subcalls.
*/
abstract contract MulticallUpgradeable is Initializable, ContextUpgradeable {
function __Multicall_init() internal onlyInitializing {
}
function __Multicall_init_unchained() internal onlyInitializing {
}
/**
* @dev Receives and executes a batch of function calls on this contract.
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
bytes memory context = msg.sender == _msgSender()
? new bytes(0)
: msg.data[msg.data.length - _contextSuffixLength():];
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
}
return results;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/draft-IERC6093.sol)
pragma solidity >=0.8.4;
/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1967.sol)
pragma solidity >=0.4.11;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC5267.sol)
pragma solidity >=0.4.16;
interface IERC5267 {
/**
* @dev MAY be emitted to signal that the domain could have changed.
*/
event EIP712DomainChanged();
/**
* @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
* signature.
*/
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/beacon/IBeacon.sol)
pragma solidity >=0.4.16;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {UpgradeableBeacon} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.22;
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
* encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
*
* Requirements:
*
* - If `data` is empty, `msg.value` must be zero.
*/
constructor(address implementation, bytes memory _data) payable {
ERC1967Utils.upgradeToAndCall(implementation, _data);
}
/**
* @dev Returns the current implementation address.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _implementation() internal view virtual override returns (address) {
return ERC1967Utils.getImplementation();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/ERC1967/ERC1967Utils.sol)
pragma solidity ^0.8.21;
import {IBeacon} from "../beacon/IBeacon.sol";
import {IERC1967} from "../../interfaces/IERC1967.sol";
import {Address} from "../../utils/Address.sol";
import {StorageSlot} from "../../utils/StorageSlot.sol";
/**
* @dev This library provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
*/
library ERC1967Utils {
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev The `implementation` of the proxy is invalid.
*/
error ERC1967InvalidImplementation(address implementation);
/**
* @dev The `admin` of the proxy is invalid.
*/
error ERC1967InvalidAdmin(address admin);
/**
* @dev The `beacon` of the proxy is invalid.
*/
error ERC1967InvalidBeacon(address beacon);
/**
* @dev An upgrade function sees `msg.value > 0` that may be lost.
*/
error ERC1967NonPayable();
/**
* @dev Returns the current implementation address.
*/
function getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
if (newImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(newImplementation);
}
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Performs implementation upgrade with additional setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) internal {
_setImplementation(newImplementation);
emit IERC1967.Upgraded(newImplementation);
if (data.length > 0) {
Address.functionDelegateCall(newImplementation, data);
} else {
_checkNonPayable();
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
* the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the ERC-1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
if (newAdmin == address(0)) {
revert ERC1967InvalidAdmin(address(0));
}
StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {IERC1967-AdminChanged} event.
*/
function changeAdmin(address newAdmin) internal {
emit IERC1967.AdminChanged(getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
*/
// solhint-disable-next-line private-vars-leading-underscore
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the ERC-1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
if (newBeacon.code.length == 0) {
revert ERC1967InvalidBeacon(newBeacon);
}
StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
address beaconImplementation = IBeacon(newBeacon).implementation();
if (beaconImplementation.code.length == 0) {
revert ERC1967InvalidImplementation(beaconImplementation);
}
}
/**
* @dev Change the beacon and trigger a setup call if data is nonempty.
* This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
* to avoid stuck value in the contract.
*
* Emits an {IERC1967-BeaconUpgraded} event.
*
* CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
* it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
* efficiency.
*/
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
_setBeacon(newBeacon);
emit IERC1967.BeaconUpgraded(newBeacon);
if (data.length > 0) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
} else {
_checkNonPayable();
}
}
/**
* @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
* if an upgrade doesn't perform an initialization call.
*/
function _checkNonPayable() private {
if (msg.value > 0) {
revert ERC1967NonPayable();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
pragma solidity ^0.8.20;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback
* function and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.22;
import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
import {Ownable} from "../../access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address,address)`
* and `upgradeAndCall(address,address,bytes)` are present, and `upgrade` must be used if no function should be called,
* while `upgradeAndCall` will invoke the `receive` function if the third argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeAndCall(address,address,bytes)` is present, and the third argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev Sets the initial owner who can perform upgrades.
*/
constructor(address initialOwner) Ownable(initialOwner) {}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
* See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
* - If `data` is empty, `msg.value` must be zero.
*/
function upgradeAndCall(
ITransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Address.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
(bool success, bytes memory returndata) = recipient.call{value: amount}("");
if (!success) {
_revert(returndata);
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {Errors.FailedCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
* of an unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {Errors.FailedCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly ("memory-safe") {
revert(add(returndata, 0x20), mload(returndata))
}
} else {
revert Errors.FailedCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
abstract contract EIP712 is IERC5267 {
using ShortStrings for *;
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
address private immutable _cachedThis;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
ShortString private immutable _name;
ShortString private immutable _version;
// slither-disable-next-line constable-states
string private _nameFallback;
// slither-disable-next-line constable-states
string private _versionFallback;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
_cachedThis = address(this);
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/// @inheritdoc IERC5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: By default this function reads _name which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Name() internal view returns (string memory) {
return _name.toStringWithFallback(_nameFallback);
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: By default this function reads _version which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Version() internal view returns (string memory) {
return _version.toStringWithFallback(_versionFallback);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an ERC-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// 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 compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;
if (aa >= (1 << 128)) {
aa >>= 128;
xn <<= 64;
}
if (aa >= (1 << 64)) {
aa >>= 64;
xn <<= 32;
}
if (aa >= (1 << 32)) {
aa >>= 32;
xn <<= 16;
}
if (aa >= (1 << 16)) {
aa >>= 16;
xn <<= 8;
}
if (aa >= (1 << 8)) {
aa >>= 8;
xn <<= 4;
}
if (aa >= (1 << 4)) {
aa >>= 4;
xn <<= 2;
}
if (aa >= (1 << 2)) {
xn <<= 1;
}
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
//
// We can refine our estimation by noticing that the middle of that interval minimizes the error.
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
// This is going to be our x_0 (and ε_0)
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
// From here, Newton's method give us:
// x_{n+1} = (x_n + a / x_n) / 2
//
// One should note that:
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
// = ((x_n² + a) / (2 * x_n))² - a
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
// = (x_n² - a)² / (2 * x_n)²
// = ((x_n² - a) / (2 * x_n))²
// ≥ 0
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
//
// This gives us the proof of quadratic convergence of the sequence:
// ε_{n+1} = | x_{n+1} - sqrt(a) |
// = | (x_n + a / x_n) / 2 - sqrt(a) |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
// = | (x_n - sqrt(a))² / (2 * x_n) |
// = | ε_n² / (2 * x_n) |
// = ε_n² / | (2 * x_n) |
//
// For the first iteration, we have a special case where x_0 is known:
// ε_1 = ε_0² / | (2 * x_0) |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
// ≤ 2**(2*e-4) / (3 * 2**(e-1))
// ≤ 2**(e-3) / 3
// ≤ 2**(e-3-log2(3))
// ≤ 2**(e-4.5)
//
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
// ε_{n+1} = ε_n² / | (2 * x_n) |
// ≤ (2**(e-k))² / (2 * 2**(e-1))
// ≤ 2**(2*e-2*k) / 2**e
// ≤ 2**(e-2*k)
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
// sqrt(a) or sqrt(a) + 1.
return xn - SafeCast.toUint(xn > a / xn);
}
}
/**
* @dev Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;
// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
}
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @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, int256 a, int256 b) internal pure returns (int256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
}
}
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
// Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
// taking advantage of the most significant (or "sign" bit) in two's complement representation.
// This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
// the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
int256 mask = n >> 255;
// A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
return uint256((n + mask) ^ mask);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Multicall.sol)
pragma solidity ^0.8.20;
import {Address} from "./Address.sol";
import {Context} from "./Context.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* Consider any assumption about calldata validation performed by the sender may be violated if it's not especially
* careful about sending transactions invoking {multicall}. For example, a relay address that filters function
* selectors won't filter calls nested within a {multicall} operation.
*
* NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {Context-_msgSender}).
* If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data`
* to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of
* {Context-_msgSender} are not propagated to subcalls.
*/
abstract contract Multicall is Context {
/**
* @dev Receives and executes a batch of function calls on this contract.
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
bytes memory context = msg.sender == _msgSender()
? new bytes(0)
: msg.data[msg.data.length - _contextSuffixLength():];
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
}
return results;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper library for emitting standardized panic codes.
*
* ```solidity
* contract Example {
* using Panic for uint256;
*
* // Use any of the declared internal constants
* function foo() { Panic.GENERIC.panic(); }
*
* // Alternatively
* function foo() { Panic.panic(Panic.GENERIC); }
* }
* ```
*
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
*
* _Available since v5.1._
*/
// slither-disable-next-line unused-state
library Panic {
/// @dev generic / unspecified error
uint256 internal constant GENERIC = 0x00;
/// @dev used by the assert() builtin
uint256 internal constant ASSERT = 0x01;
/// @dev arithmetic underflow or overflow
uint256 internal constant UNDER_OVERFLOW = 0x11;
/// @dev division or modulo by zero
uint256 internal constant DIVISION_BY_ZERO = 0x12;
/// @dev enum conversion error
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
/// @dev invalid encoding in storage
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
/// @dev empty array pop
uint256 internal constant EMPTY_ARRAY_POP = 0x31;
/// @dev array out of bounds access
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
/// @dev resource error (too large allocation or too large array)
uint256 internal constant RESOURCE_ERROR = 0x41;
/// @dev calling invalid internal function
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
/// @dev Reverts with a panic code. Recommended to use with
/// the internal constants with predefined codes.
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71)
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)
pragma solidity ^0.8.20;
import {StorageSlot} from "./StorageSlot.sol";
// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
// | length | 0x BB |
type ShortString is bytes32;
/**
* @dev This library provides functions to convert short memory strings
* into a `ShortString` type that can be used as an immutable variable.
*
* Strings of arbitrary length can be optimized using this library if
* they are short enough (up to 31 bytes) by packing them with their
* length (1 byte) in a single EVM word (32 bytes). Additionally, a
* fallback mechanism can be used for every other case.
*
* Usage example:
*
* ```solidity
* contract Named {
* using ShortStrings for *;
*
* ShortString private immutable _name;
* string private _nameFallback;
*
* constructor(string memory contractName) {
* _name = contractName.toShortStringWithFallback(_nameFallback);
* }
*
* function name() external view returns (string memory) {
* return _name.toStringWithFallback(_nameFallback);
* }
* }
* ```
*/
library ShortStrings {
// Used as an identifier for strings longer than 31 bytes.
bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
error StringTooLong(string str);
error InvalidShortString();
/**
* @dev Encode a string of at most 31 chars into a `ShortString`.
*
* This will trigger a `StringTooLong` error is the input string is too long.
*/
function toShortString(string memory str) internal pure returns (ShortString) {
bytes memory bstr = bytes(str);
if (bstr.length > 31) {
revert StringTooLong(str);
}
return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
}
/**
* @dev Decode a `ShortString` back to a "normal" string.
*/
function toString(ShortString sstr) internal pure returns (string memory) {
uint256 len = byteLength(sstr);
// using `new string(len)` would work locally but is not memory safe.
string memory str = new string(32);
assembly ("memory-safe") {
mstore(str, len)
mstore(add(str, 0x20), sstr)
}
return str;
}
/**
* @dev Return the length of a `ShortString`.
*/
function byteLength(ShortString sstr) internal pure returns (uint256) {
uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
if (result > 31) {
revert InvalidShortString();
}
return result;
}
/**
* @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
*/
function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
if (bytes(value).length < 32) {
return toShortString(value);
} else {
StorageSlot.getStringSlot(store).value = value;
return ShortString.wrap(FALLBACK_SENTINEL);
}
}
/**
* @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.
*/
function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return toString(value);
} else {
return store;
}
}
/**
* @dev Return the length of a string that was encoded to `ShortString` or written to storage using
* {toShortStringWithFallback}.
*
* WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
* actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
*/
function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
return byteLength(value);
} else {
return bytes(store).length;
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.20;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC-1967 implementation slot:
* ```solidity
* contract ERC1967 {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(newImplementation.code.length > 0);
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct Int256Slot {
int256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `Int256Slot` with member `value` located at `slot`.
*/
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns a `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
/**
* @dev Returns a `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
assembly ("memory-safe") {
r.slot := store.slot
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(add(buffer, 0x20), length)
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @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))
}
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataService } from "@graphprotocol/interfaces/contracts/data-service/IDataService.sol";
import { DataServiceV1Storage } from "./DataServiceStorage.sol";
import { GraphDirectory } from "../utilities/GraphDirectory.sol";
import { ProvisionManager } from "./utilities/ProvisionManager.sol";
/**
* @title DataService contract
* @dev Implementation of the {IDataService} interface.
* @notice This implementation provides base functionality for a data service:
* - GraphDirectory, allows the data service to interact with Graph Horizon contracts
* - ProvisionManager, provides functionality to manage provisions
*
* The derived contract MUST implement all the interfaces described in {IDataService} and in
* accordance with the Data Service framework.
* @dev A note on upgradeability: this base contract can be inherited by upgradeable or non upgradeable
* contracts.
* - If the data service implementation is upgradeable, it must initialize the contract via an external
* initializer function with the `initializer` modifier that calls {__DataService_init} or
* {__DataService_init_unchained}. It's recommended the implementation constructor to also call
* {_disableInitializers} to prevent the implementation from being initialized.
* - If the data service implementation is NOT upgradeable, it must initialize the contract by calling
* {__DataService_init} or {__DataService_init_unchained} in the constructor. Note that the `initializer`
* will be required in the constructor.
* - Note that in both cases if using {__DataService_init_unchained} variant the corresponding parent
* initializers must be called in the implementation.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataService is GraphDirectory, ProvisionManager, DataServiceV1Storage, IDataService {
/**
* @dev Addresses in GraphDirectory are immutables, they can only be set in this constructor.
* @param controller The address of the Graph Horizon controller contract.
*/
constructor(address controller) GraphDirectory(controller) {}
/// @inheritdoc IDataService
function getThawingPeriodRange() external view returns (uint64, uint64) {
return _getThawingPeriodRange();
}
/// @inheritdoc IDataService
function getVerifierCutRange() external view returns (uint32, uint32) {
return _getVerifierCutRange();
}
/// @inheritdoc IDataService
function getProvisionTokensRange() external view returns (uint256, uint256) {
return _getProvisionTokensRange();
}
/// @inheritdoc IDataService
function getDelegationRatio() external view returns (uint32) {
return _getDelegationRatio();
}
/**
* @notice Initializes the contract and any parent contracts.
*/
function __DataService_init() internal onlyInitializing {
__ProvisionManager_init_unchained();
__DataService_init_unchained();
}
/**
* @notice Initializes the contract.
*/
function __DataService_init_unchained() internal onlyInitializing {}
}// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; /** * @title DataServiceStorage * @dev This contract holds the storage variables for the DataService contract. * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ abstract contract DataServiceV1Storage { /// @dev Gap to allow adding variables in future upgrades /// Note that this contract is not upgradeable but might be inherited by an upgradeable contract uint256[50] private __gap; }
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataServiceFees } from "@graphprotocol/interfaces/contracts/data-service/IDataServiceFees.sol";
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
import { ProvisionTracker } from "../libraries/ProvisionTracker.sol";
import { LinkedList } from "../../libraries/LinkedList.sol";
import { DataService } from "../DataService.sol";
import { DataServiceFeesV1Storage } from "./DataServiceFeesStorage.sol";
/**
* @title DataServiceFees contract
* @dev Implementation of the {IDataServiceFees} interface.
* @notice Extension for the {IDataService} contract to handle payment collateralization
* using a Horizon provision. See {IDataServiceFees} for more details.
* @dev This contract inherits from {DataService} which needs to be initialized, please see
* {DataService} for detailed instructions.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataServiceFees is DataService, DataServiceFeesV1Storage, IDataServiceFees {
using ProvisionTracker for mapping(address => uint256);
using LinkedList for ILinkedList.List;
/// @inheritdoc IDataServiceFees
function releaseStake(uint256 numClaimsToRelease) external virtual override {
_releaseStake(msg.sender, numClaimsToRelease);
}
/**
* @notice Locks stake for a service provider to back a payment.
* Creates a stake claim, which is stored in a linked list by service provider.
* @dev Requirements:
* - The associated provision must have enough available tokens to lock the stake.
*
* Emits a {StakeClaimLocked} event.
*
* @param _serviceProvider The address of the service provider
* @param _tokens The amount of tokens to lock in the claim
* @param _unlockTimestamp The timestamp when the tokens can be released
*/
function _lockStake(address _serviceProvider, uint256 _tokens, uint256 _unlockTimestamp) internal {
require(_tokens != 0, DataServiceFeesZeroTokens());
feesProvisionTracker.lock(_graphStaking(), _serviceProvider, _tokens, _delegationRatio);
ILinkedList.List storage claimsList = claimsLists[_serviceProvider];
// Save item and add to list
bytes32 claimId = _buildStakeClaimId(_serviceProvider, claimsList.nonce);
claims[claimId] = StakeClaim({
tokens: _tokens,
createdAt: block.timestamp,
releasableAt: _unlockTimestamp,
nextClaim: bytes32(0)
});
if (claimsList.count != 0) claims[claimsList.tail].nextClaim = claimId;
claimsList.addTail(claimId);
emit StakeClaimLocked(_serviceProvider, claimId, _tokens, _unlockTimestamp);
}
/**
* @notice Releases expired stake claims for a service provider.
* @dev This function can be overriden and/or disabled.
* @dev Note that the list is traversed by creation date not by releasableAt date. Traversing will stop
* when the first stake claim that is not yet expired is found even if later stake claims have expired. This
* could happen if stake claims are genereted with different unlock periods.
* @dev Emits a {StakeClaimsReleased} event, and a {StakeClaimReleased} event for each claim released.
* @param _serviceProvider The address of the service provider
* @param _numClaimsToRelease Amount of stake claims to process. If 0, all stake claims are processed.
*/
function _releaseStake(address _serviceProvider, uint256 _numClaimsToRelease) internal {
ILinkedList.List storage claimsList = claimsLists[_serviceProvider];
(uint256 claimsReleased, bytes memory data) = claimsList.traverse(
_getNextStakeClaim,
_processStakeClaim,
_deleteStakeClaim,
abi.encode(0, _serviceProvider),
_numClaimsToRelease
);
emit StakeClaimsReleased(_serviceProvider, claimsReleased, abi.decode(data, (uint256)));
}
/**
* @notice Processes a stake claim, releasing the tokens if the claim has expired.
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param _claimId The id of the stake claim
* @param _acc The accumulator for the stake claims being processed
* @return Whether the stake claim is still locked, indicating that the traversal should continue or stop.
* @return The updated accumulator data
*/
function _processStakeClaim(bytes32 _claimId, bytes memory _acc) private returns (bool, bytes memory) {
StakeClaim memory claim = _getStakeClaim(_claimId);
// early exit
if (claim.releasableAt > block.timestamp) {
return (true, LinkedList.NULL_BYTES);
}
// decode
(uint256 tokensClaimed, address serviceProvider) = abi.decode(_acc, (uint256, address));
// process
feesProvisionTracker.release(serviceProvider, claim.tokens);
emit StakeClaimReleased(serviceProvider, _claimId, claim.tokens, claim.releasableAt);
// encode
_acc = abi.encode(tokensClaimed + claim.tokens, serviceProvider);
return (false, _acc);
}
/**
* @notice Deletes a stake claim.
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param _claimId The ID of the stake claim to delete
*/
function _deleteStakeClaim(bytes32 _claimId) private {
delete claims[_claimId];
}
/**
* @notice Gets the details of a stake claim
* @param _claimId The ID of the stake claim
* @return The stake claim details
*/
function _getStakeClaim(bytes32 _claimId) private view returns (StakeClaim memory) {
StakeClaim memory claim = claims[_claimId];
require(claim.createdAt != 0, DataServiceFeesClaimNotFound(_claimId));
return claim;
}
/**
* @notice Gets the next stake claim in the linked list
* @dev This function is used as a callback in the stake claims linked list traversal.
* @param _claimId The ID of the stake claim
* @return The next stake claim ID
*/
function _getNextStakeClaim(bytes32 _claimId) private view returns (bytes32) {
return claims[_claimId].nextClaim;
}
/**
* @notice Builds a stake claim ID
* @param _serviceProvider The address of the service provider
* @param _nonce A nonce of the stake claim
* @return The stake claim ID
*/
function _buildStakeClaimId(address _serviceProvider, uint256 _nonce) private view returns (bytes32) {
return keccak256(abi.encodePacked(address(this), _serviceProvider, _nonce));
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataServiceFees } from "@graphprotocol/interfaces/contracts/data-service/IDataServiceFees.sol";
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
/**
* @title Storage layout for the {DataServiceFees} extension contract.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataServiceFeesV1Storage {
/// @notice The amount of tokens locked in stake claims for each service provider
mapping(address serviceProvider => uint256 tokens) public feesProvisionTracker;
/// @notice List of all locked stake claims to be released to service providers
mapping(bytes32 claimId => IDataServiceFees.StakeClaim claim) public claims;
/// @notice Service providers registered in the data service
mapping(address serviceProvider => ILinkedList.List list) public claimsLists;
/// @dev Gap to allow adding variables in future upgrades
/// Note that this contract is not upgradeable but might be inherited by an upgradeable contract
uint256[50] private __gap;
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataServicePausable } from "@graphprotocol/interfaces/contracts/data-service/IDataServicePausable.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { DataService } from "../DataService.sol";
/**
* @title DataServicePausable contract
* @dev Implementation of the {IDataServicePausable} interface.
* @notice Extension for the {IDataService} contract, adds pausing functionality
* to the data service. Pausing is controlled by privileged accounts called
* pause guardians.
* @dev Note that this extension does not provide an external function to set pause
* guardians. This should be implemented in the derived contract.
* @dev This contract inherits from {DataService} which needs to be initialized, please see
* {DataService} for detailed instructions.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataServicePausable is Pausable, DataService, IDataServicePausable {
/// @notice List of pause guardians and their allowed status
mapping(address pauseGuardian => bool allowed) public pauseGuardians;
/**
* @notice Checks if the caller is a pause guardian.
*/
modifier onlyPauseGuardian() {
require(pauseGuardians[msg.sender], DataServicePausableNotPauseGuardian(msg.sender));
_;
}
/// @inheritdoc IDataServicePausable
function pause() external override onlyPauseGuardian {
_pause();
}
/// @inheritdoc IDataServicePausable
function unpause() external override onlyPauseGuardian {
_unpause();
}
/**
* @notice Sets a pause guardian.
* @dev Internal function to be used by the derived contract to set pause guardians.
* @param _pauseGuardian The address of the pause guardian
* @param _allowed The allowed status of the pause guardian
*/
function _setPauseGuardian(address _pauseGuardian, bool _allowed) internal {
require(
pauseGuardians[_pauseGuardian] == !_allowed,
DataServicePausablePauseGuardianNoChange(_pauseGuardian, _allowed)
);
pauseGuardians[_pauseGuardian] = _allowed;
emit PauseGuardianSet(_pauseGuardian, _allowed);
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IDataServicePausable } from "@graphprotocol/interfaces/contracts/data-service/IDataServicePausable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import { DataService } from "../DataService.sol";
/**
* @title DataServicePausableUpgradeable contract
* @dev Implementation of the {IDataServicePausable} interface.
* @dev Upgradeable version of the {DataServicePausable} contract.
* @dev This contract inherits from {DataService} which needs to be initialized, please see
* {DataService} for detailed instructions.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataServicePausableUpgradeable is PausableUpgradeable, DataService, IDataServicePausable {
/// @notice List of pause guardians and their allowed status
mapping(address pauseGuardian => bool allowed) public pauseGuardians;
/// @dev Gap to allow adding variables in future upgrades
uint256[50] private __gap;
/**
* @notice Checks if the caller is a pause guardian.
*/
modifier onlyPauseGuardian() {
require(pauseGuardians[msg.sender], DataServicePausableNotPauseGuardian(msg.sender));
_;
}
/// @inheritdoc IDataServicePausable
function pause() external override onlyPauseGuardian {
_pause();
}
/// @inheritdoc IDataServicePausable
function unpause() external override onlyPauseGuardian {
_unpause();
}
/**
* @notice Initializes the contract and parent contracts
*/
function __DataServicePausable_init() internal onlyInitializing {
__Pausable_init_unchained();
__DataServicePausable_init_unchained();
}
/**
* @notice Initializes the contract
*/
function __DataServicePausable_init_unchained() internal onlyInitializing {}
/**
* @notice Sets a pause guardian.
* @dev Internal function to be used by the derived contract to set pause guardians.
*
* Emits a {PauseGuardianSet} event.
*
* @param _pauseGuardian The address of the pause guardian
* @param _allowed The allowed status of the pause guardian
*/
function _setPauseGuardian(address _pauseGuardian, bool _allowed) internal {
require(
pauseGuardians[_pauseGuardian] == !_allowed,
DataServicePausablePauseGuardianNoChange(_pauseGuardian, _allowed)
);
pauseGuardians[_pauseGuardian] = _allowed;
emit PauseGuardianSet(_pauseGuardian, _allowed);
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IDataServiceRescuable } from "@graphprotocol/interfaces/contracts/data-service/IDataServiceRescuable.sol";
import { DataService } from "../DataService.sol";
import { Denominations } from "../../libraries/Denominations.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title Rescuable contract
* @dev Allows a contract to have a function to rescue tokens sent by mistake.
* The contract must implement the external rescueTokens function or similar,
* that calls this contract's _rescueTokens.
* @dev Note that this extension does not provide an external function to set
* rescuers. This should be implemented in the derived contract.
* @dev This contract inherits from {DataService} which needs to be initialized, please see
* {DataService} for detailed instructions.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract DataServiceRescuable is DataService, IDataServiceRescuable {
/// @notice List of rescuers and their allowed status
mapping(address rescuer => bool allowed) public rescuers;
/// @dev Gap to allow adding variables in future upgrades
/// Note that this contract is not upgradeable but might be inherited by an upgradeable contract
uint256[50] private __gap;
/**
* @notice Checks if the caller is a rescuer.
*/
modifier onlyRescuer() {
require(rescuers[msg.sender], DataServiceRescuableNotRescuer(msg.sender));
_;
}
/// @inheritdoc IDataServiceRescuable
function rescueGRT(address to, uint256 tokens) external virtual onlyRescuer {
_rescueTokens(to, address(_graphToken()), tokens);
}
/// @inheritdoc IDataServiceRescuable
function rescueETH(address payable to, uint256 tokens) external virtual onlyRescuer {
_rescueTokens(to, Denominations.NATIVE_TOKEN, tokens);
}
/**
* @notice Sets a rescuer.
* @dev Internal function to be used by the derived contract to set rescuers.
*
* Emits a {RescuerSet} event.
*
* @param _rescuer Address of the rescuer
* @param _allowed Allowed status of the rescuer
*/
function _setRescuer(address _rescuer, bool _allowed) internal {
rescuers[_rescuer] = _allowed;
emit RescuerSet(_rescuer, _allowed);
}
/**
* @dev Allows rescuing tokens sent to this contract
* @param _to Destination address to send the tokens
* @param _token Address of the token being rescued
* @param _tokens Amount of tokens to pull
*/
function _rescueTokens(address _to, address _token, uint256 _tokens) internal {
require(_tokens != 0, DataServiceRescuableCannotRescueZero());
if (Denominations.isNativeToken(_token)) Address.sendValue(payable(_to), _tokens);
else SafeERC20.safeTransfer(IERC20(_token), _to, _tokens);
emit TokensRescued(msg.sender, _to, _token, _tokens);
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol";
/**
* @title ProvisionTracker library
* @notice A library to facilitate tracking of "used tokens" on Graph Horizon provisions. This can be used to
* ensure data services have enough economic security (provisioned stake) to back the payments they collect for
* their services.
* The library provides two primitives, lock and release to signal token usage and free up tokens respectively. It
* does not make any assumptions about the conditions under which tokens are locked or released.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
library ProvisionTracker {
/**
* @notice Thrown when trying to lock more tokens than available
* @param tokensAvailable The amount of tokens available
* @param tokensRequired The amount of tokens required
*/
error ProvisionTrackerInsufficientTokens(uint256 tokensAvailable, uint256 tokensRequired);
/**
* @notice Locks tokens for a service provider
* @dev Requirements:
* - `tokens` must be less than or equal to the amount of tokens available, as reported by the HorizonStaking contract
* @param self The provision tracker mapping
* @param graphStaking The HorizonStaking contract
* @param serviceProvider The service provider address
* @param tokens The amount of tokens to lock
* @param delegationRatio A delegation ratio to limit the amount of delegation that's usable
*/
function lock(
mapping(address => uint256) storage self,
IHorizonStaking graphStaking,
address serviceProvider,
uint256 tokens,
uint32 delegationRatio
) internal {
if (tokens == 0) return;
uint256 tokensRequired = self[serviceProvider] + tokens;
uint256 tokensAvailable = graphStaking.getTokensAvailable(serviceProvider, address(this), delegationRatio);
require(tokensRequired <= tokensAvailable, ProvisionTrackerInsufficientTokens(tokensAvailable, tokensRequired));
self[serviceProvider] += tokens;
}
/**
* @notice Releases tokens for a service provider
* @dev Requirements:
* - `tokens` must be less than or equal to the amount of tokens locked for the service provider
* @param self The provision tracker mapping
* @param serviceProvider The service provider address
* @param tokens The amount of tokens to release
*/
function release(mapping(address => uint256) storage self, address serviceProvider, uint256 tokens) internal {
if (tokens == 0) return;
require(self[serviceProvider] >= tokens, ProvisionTrackerInsufficientTokens(self[serviceProvider], tokens));
self[serviceProvider] -= tokens;
}
/**
* @notice Checks if a service provider has enough tokens available to lock
* @param self The provision tracker mapping
* @param graphStaking The HorizonStaking contract
* @param serviceProvider The service provider address
* @param delegationRatio A delegation ratio to limit the amount of delegation that's usable
* @return true if the service provider has enough tokens available to lock, false otherwise
*/
function check(
mapping(address => uint256) storage self,
IHorizonStaking graphStaking,
address serviceProvider,
uint32 delegationRatio
) internal view returns (bool) {
uint256 tokensAvailable = graphStaking.getTokensAvailable(serviceProvider, address(this), delegationRatio);
return self[serviceProvider] <= tokensAvailable;
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol";
import { UintRange } from "../../libraries/UintRange.sol";
import { PPMMath } from "../../libraries/PPMMath.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { GraphDirectory } from "../../utilities/GraphDirectory.sol";
import { ProvisionManagerV1Storage } from "./ProvisionManagerStorage.sol";
/**
* @title ProvisionManager contract
* @notice A helper contract that implements several provision management functions.
* @dev Provides utilities to verify provision parameters are within an acceptable range. Each
* parameter has an overridable setter and getter for the validity range, and a checker that reverts
* if the parameter is out of range.
* The parameters are:
* - Provision parameters (thawing period and verifier cut)
* - Provision tokens
*
* Note that default values for all provision parameters provide the most permissive configuration, it's
* highly recommended to override them at the data service level.
*
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract ProvisionManager is Initializable, GraphDirectory, ProvisionManagerV1Storage {
using UintRange for uint256;
/// @notice The default minimum verifier cut.
uint32 internal constant DEFAULT_MIN_VERIFIER_CUT = type(uint32).min;
/// @notice The default maximum verifier cut.
uint32 internal constant DEFAULT_MAX_VERIFIER_CUT = uint32(PPMMath.MAX_PPM);
/// @notice The default minimum thawing period.
uint64 internal constant DEFAULT_MIN_THAWING_PERIOD = type(uint64).min;
/// @notice The default maximum thawing period.
uint64 internal constant DEFAULT_MAX_THAWING_PERIOD = type(uint64).max;
/// @notice The default minimum provision tokens.
uint256 internal constant DEFAULT_MIN_PROVISION_TOKENS = type(uint256).min;
/// @notice The default maximum provision tokens.
uint256 internal constant DEFAULT_MAX_PROVISION_TOKENS = type(uint256).max;
/// @notice The default delegation ratio.
uint32 internal constant DEFAULT_DELEGATION_RATIO = type(uint32).max;
/**
* @notice Emitted when the provision tokens range is set.
* @param min The minimum allowed value for the provision tokens.
* @param max The maximum allowed value for the provision tokens.
*/
event ProvisionTokensRangeSet(uint256 min, uint256 max);
/**
* @notice Emitted when the delegation ratio is set.
* @param ratio The delegation ratio
*/
event DelegationRatioSet(uint32 ratio);
/**
* @notice Emitted when the verifier cut range is set.
* @param min The minimum allowed value for the max verifier cut.
* @param max The maximum allowed value for the max verifier cut.
*/
event VerifierCutRangeSet(uint32 min, uint32 max);
/**
* @notice Emitted when the thawing period range is set.
* @param min The minimum allowed value for the thawing period.
* @param max The maximum allowed value for the thawing period.
*/
event ThawingPeriodRangeSet(uint64 min, uint64 max);
/**
* @notice Thrown when a provision parameter is out of range.
* @param message The error message.
* @param value The value that is out of range.
* @param min The minimum allowed value.
* @param max The maximum allowed value.
*/
error ProvisionManagerInvalidValue(bytes message, uint256 value, uint256 min, uint256 max);
/**
* @notice Thrown when attempting to set a range where min is greater than max.
* @param min The minimum value.
* @param max The maximum value.
*/
error ProvisionManagerInvalidRange(uint256 min, uint256 max);
/**
* @notice Thrown when the caller is not authorized to manage the provision of a service provider.
* @param serviceProvider The address of the serviceProvider.
* @param caller The address of the caller.
*/
error ProvisionManagerNotAuthorized(address serviceProvider, address caller);
/**
* @notice Thrown when a provision is not found.
* @param serviceProvider The address of the service provider.
*/
error ProvisionManagerProvisionNotFound(address serviceProvider);
/**
* @notice Checks if the caller is authorized to manage the provision of a service provider.
* @param serviceProvider The address of the service provider.
*/
modifier onlyAuthorizedForProvision(address serviceProvider) {
require(
_graphStaking().isAuthorized(serviceProvider, address(this), msg.sender),
ProvisionManagerNotAuthorized(serviceProvider, msg.sender)
);
_;
}
/**
* @notice Checks if a provision of a service provider is valid according
* to the parameter ranges established.
* @param serviceProvider The address of the service provider.
*/
modifier onlyValidProvision(address serviceProvider) virtual {
IHorizonStaking.Provision memory provision = _getProvision(serviceProvider);
_checkProvisionTokens(provision);
_checkProvisionParameters(provision, false);
_;
}
/**
* @notice Initializes the contract and any parent contracts.
*/
function __ProvisionManager_init() internal onlyInitializing {
__ProvisionManager_init_unchained();
}
/**
* @notice Initializes the contract.
* @dev All parameters set to their entire range as valid.
*/
function __ProvisionManager_init_unchained() internal onlyInitializing {
_setProvisionTokensRange(DEFAULT_MIN_PROVISION_TOKENS, DEFAULT_MAX_PROVISION_TOKENS);
_setVerifierCutRange(DEFAULT_MIN_VERIFIER_CUT, DEFAULT_MAX_VERIFIER_CUT);
_setThawingPeriodRange(DEFAULT_MIN_THAWING_PERIOD, DEFAULT_MAX_THAWING_PERIOD);
_setDelegationRatio(DEFAULT_DELEGATION_RATIO);
}
/**
* @notice Verifies and accepts the provision parameters of a service provider in
* the {HorizonStaking} contract.
* @dev Checks the pending provision parameters, not the current ones.
*
* @param _serviceProvider The address of the service provider.
*/
function _acceptProvisionParameters(address _serviceProvider) internal {
_checkProvisionParameters(_serviceProvider, true);
_graphStaking().acceptProvisionParameters(_serviceProvider);
}
// -- setters --
/**
* @notice Sets the delegation ratio.
* @param _ratio The delegation ratio to be set
*/
function _setDelegationRatio(uint32 _ratio) internal {
_delegationRatio = _ratio;
emit DelegationRatioSet(_ratio);
}
/**
* @notice Sets the range for the provision tokens.
* @param _min The minimum allowed value for the provision tokens.
* @param _max The maximum allowed value for the provision tokens.
*/
function _setProvisionTokensRange(uint256 _min, uint256 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
_minimumProvisionTokens = _min;
_maximumProvisionTokens = _max;
emit ProvisionTokensRangeSet(_min, _max);
}
/**
* @notice Sets the range for the verifier cut.
* @param _min The minimum allowed value for the max verifier cut.
* @param _max The maximum allowed value for the max verifier cut.
*/
function _setVerifierCutRange(uint32 _min, uint32 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
require(PPMMath.isValidPPM(_max), ProvisionManagerInvalidRange(_min, _max));
_minimumVerifierCut = _min;
_maximumVerifierCut = _max;
emit VerifierCutRangeSet(_min, _max);
}
/**
* @notice Sets the range for the thawing period.
* @param _min The minimum allowed value for the thawing period.
* @param _max The maximum allowed value for the thawing period.
*/
function _setThawingPeriodRange(uint64 _min, uint64 _max) internal {
require(_min <= _max, ProvisionManagerInvalidRange(_min, _max));
_minimumThawingPeriod = _min;
_maximumThawingPeriod = _max;
emit ThawingPeriodRangeSet(_min, _max);
}
// -- checks --
/**
* @notice Checks if the provision tokens of a service provider are within the valid range.
* @param _serviceProvider The address of the service provider.
*/
function _checkProvisionTokens(address _serviceProvider) internal view virtual {
IHorizonStaking.Provision memory provision = _getProvision(_serviceProvider);
_checkProvisionTokens(provision);
}
/**
* @notice Checks if the provision tokens of a service provider are within the valid range.
* Note that thawing tokens are not considered in this check.
* @param _provision The provision to check.
*/
function _checkProvisionTokens(IHorizonStaking.Provision memory _provision) internal view virtual {
_checkValueInRange(
_provision.tokens - _provision.tokensThawing,
_minimumProvisionTokens,
_maximumProvisionTokens,
"tokens"
);
}
/**
* @notice Checks if the provision parameters of a service provider are within the valid range.
* @param _serviceProvider The address of the service provider.
* @param _checkPending If true, checks the pending provision parameters.
*/
function _checkProvisionParameters(address _serviceProvider, bool _checkPending) internal view virtual {
IHorizonStaking.Provision memory provision = _getProvision(_serviceProvider);
_checkProvisionParameters(provision, _checkPending);
}
/**
* @notice Checks if the provision parameters of a service provider are within the valid range.
* @param _provision The provision to check.
* @param _checkPending If true, checks the pending provision parameters instead of the current ones.
*/
function _checkProvisionParameters(
IHorizonStaking.Provision memory _provision,
bool _checkPending
) internal view virtual {
(uint64 thawingPeriodMin, uint64 thawingPeriodMax) = _getThawingPeriodRange();
uint64 thawingPeriodToCheck = _checkPending ? _provision.thawingPeriodPending : _provision.thawingPeriod;
_checkValueInRange(thawingPeriodToCheck, thawingPeriodMin, thawingPeriodMax, "thawingPeriod");
(uint32 verifierCutMin, uint32 verifierCutMax) = _getVerifierCutRange();
uint32 maxVerifierCutToCheck = _checkPending ? _provision.maxVerifierCutPending : _provision.maxVerifierCut;
_checkValueInRange(maxVerifierCutToCheck, verifierCutMin, verifierCutMax, "maxVerifierCut");
}
// -- getters --
/**
* @notice Gets the delegation ratio.
* @return The delegation ratio
*/
function _getDelegationRatio() internal view returns (uint32) {
return _delegationRatio;
}
/**
* @notice Gets the range for the provision tokens.
* @return The minimum allowed value for the provision tokens.
* @return The maximum allowed value for the provision tokens.
*/
function _getProvisionTokensRange() internal view virtual returns (uint256, uint256) {
return (_minimumProvisionTokens, _maximumProvisionTokens);
}
/**
* @notice Gets the range for the thawing period.
* @return The minimum allowed value for the thawing period.
* @return The maximum allowed value for the thawing period.
*/
function _getThawingPeriodRange() internal view virtual returns (uint64, uint64) {
return (_minimumThawingPeriod, _maximumThawingPeriod);
}
/**
* @notice Gets the range for the verifier cut.
* @return The minimum allowed value for the max verifier cut.
* @return The maximum allowed value for the max verifier cut.
*/
function _getVerifierCutRange() internal view virtual returns (uint32, uint32) {
return (_minimumVerifierCut, _maximumVerifierCut);
}
/**
* @notice Gets a provision from the {HorizonStaking} contract.
* @dev Requirements:
* - The provision must exist.
* @param _serviceProvider The address of the service provider.
* @return The provision.
*/
function _getProvision(address _serviceProvider) internal view returns (IHorizonStaking.Provision memory) {
IHorizonStaking.Provision memory provision = _graphStaking().getProvision(_serviceProvider, address(this));
require(provision.createdAt != 0, ProvisionManagerProvisionNotFound(_serviceProvider));
return provision;
}
/**
* @notice Checks if a value is within a valid range.
* @param _value The value to check.
* @param _min The minimum allowed value.
* @param _max The maximum allowed value.
* @param _revertMessage The revert message to display if the value is out of range.
*/
function _checkValueInRange(uint256 _value, uint256 _min, uint256 _max, bytes memory _revertMessage) private pure {
require(_value.isInRange(_min, _max), ProvisionManagerInvalidValue(_revertMessage, _value, _min, _max));
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
/**
* @title Storage layout for the {ProvisionManager} helper contract.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract ProvisionManagerV1Storage {
/// @notice The minimum amount of tokens required to register a provision in the data service
uint256 internal _minimumProvisionTokens;
/// @notice The maximum amount of tokens allowed to register a provision in the data service
uint256 internal _maximumProvisionTokens;
/// @notice The minimum thawing period required to register a provision in the data service
uint64 internal _minimumThawingPeriod;
/// @notice The maximum thawing period allowed to register a provision in the data service
uint64 internal _maximumThawingPeriod;
/// @notice The minimum verifier cut required to register a provision in the data service (in PPM)
uint32 internal _minimumVerifierCut;
/// @notice The maximum verifier cut allowed to register a provision in the data service (in PPM)
uint32 internal _maximumVerifierCut;
/// @notice How much delegation the service provider can effectively use
/// @dev Max calculated as service provider's stake * delegationRatio
uint32 internal _delegationRatio;
/// @dev Gap to allow adding variables in future upgrades
/// Note that this contract is not upgradeable but might be inherited by an upgradeable contract
uint256[50] private __gap;
}// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; /** * @title Denominations library * @dev Provides a list of ground denominations for those tokens that cannot be represented by an ERC20. * For now, the only needed is the native token that could be ETH, MATIC, or other depending on the layer being operated. * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ library Denominations { /// @notice The address of the native token, i.e ETH /// @dev This convention is taken from https://eips.ethereum.org/EIPS/eip-7528 address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /** * @notice Checks if a token is the native token * @param token The token address to check * @return True if the token is the native token, false otherwise */ function isNativeToken(address token) internal pure returns (bool) { return token == NATIVE_TOKEN; } }
/*
Copyright 2017 Bprotocol Foundation, 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.27;
/**
* @title LibFixedMath
* @notice This library provides fixed-point arithmetic operations.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
library LibFixedMath {
// 1
int256 private constant FIXED_1 = int256(0x0000000000000000000000000000000080000000000000000000000000000000);
// 2**255
int256 private constant MIN_FIXED_VAL = type(int256).min;
// 0
int256 private constant EXP_MAX_VAL = 0;
// -63.875
int256 private constant EXP_MIN_VAL = -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000);
/// @dev Get one as a fixed-point number.
function one() internal pure returns (int256 f) {
f = FIXED_1;
}
/// @dev Returns the addition of two fixed point numbers, reverting on overflow.
function sub(int256 a, int256 b) internal pure returns (int256 c) {
if (b == MIN_FIXED_VAL) {
revert("out-of-bounds");
}
c = _add(a, -b);
}
/// @dev Returns the multiplication of two fixed point numbers, reverting on overflow.
function mul(int256 a, int256 b) internal pure returns (int256 c) {
c = _mul(a, b) / FIXED_1;
}
/// @dev Performs (a * n) / d, without scaling for precision.
function mulDiv(int256 a, int256 n, int256 d) internal pure returns (int256 c) {
c = _div(_mul(a, n), d);
}
/// @dev Returns the unsigned integer result of multiplying a fixed-point
/// number with an integer, reverting if the multiplication overflows.
/// Negative results are clamped to zero.
function uintMul(int256 f, uint256 u) internal pure returns (uint256) {
if (int256(u) < int256(0)) {
revert("out-of-bounds");
}
int256 c = _mul(f, int256(u));
if (c <= 0) {
return 0;
}
return uint256(uint256(c) >> 127);
}
/// @dev Convert signed `n` / `d` to a fixed-point number.
function toFixed(int256 n, int256 d) internal pure returns (int256 f) {
f = _div(_mul(n, FIXED_1), d);
}
/// @dev Convert a fixed-point number to an integer.
function toInteger(int256 f) internal pure returns (int256 n) {
return f / FIXED_1;
}
/// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1
function exp(int256 x) internal pure returns (int256 r) {
if (x < EXP_MIN_VAL) {
// Saturate to zero below EXP_MIN_VAL.
return 0;
}
if (x == 0) {
return FIXED_1;
}
if (x > EXP_MAX_VAL) {
revert("out-of-bounds");
}
// Rewrite the input as a product of natural exponents and a
// single residual q, where q is a number of small magnitude.
// For example: e^-34.419 = e^(-32 - 2 - 0.25 - 0.125 - 0.044)
// = e^-32 * e^-2 * e^-0.25 * e^-0.125 * e^-0.044
// -> q = -0.044
// Multiply with the taylor series for e^q
int256 y;
int256 z;
// q = x % 0.125 (the residual)
z = y = x % 0x0000000000000000000000000000000010000000000000000000000000000000;
z = (z * y) / FIXED_1;
r += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
z = (z * y) / FIXED_1;
r += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
z = (z * y) / FIXED_1;
r += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
z = (z * y) / FIXED_1;
r += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
z = (z * y) / FIXED_1;
r += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
z = (z * y) / FIXED_1;
r += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
z = (z * y) / FIXED_1;
r += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
z = (z * y) / FIXED_1;
r += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
z = (z * y) / FIXED_1;
r += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
z = (z * y) / FIXED_1;
r += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
z = (z * y) / FIXED_1;
r += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
z = (z * y) / FIXED_1;
r += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
z = (z * y) / FIXED_1;
r += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
z = (z * y) / FIXED_1;
r += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
z = (z * y) / FIXED_1;
r += z * 0x000000000001c638; // add y^16 * (20! / 16!)
z = (z * y) / FIXED_1;
r += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
z = (z * y) / FIXED_1;
r += z * 0x000000000000017c; // add y^18 * (20! / 18!)
z = (z * y) / FIXED_1;
r += z * 0x0000000000000014; // add y^19 * (20! / 19!)
z = (z * y) / FIXED_1;
r += z * 0x0000000000000001; // add y^20 * (20! / 20!)
r = r / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
// Multiply with the non-residual terms.
x = -x;
// e ^ -32
if ((x & int256(0x0000000000000000000000000000001000000000000000000000000000000000)) != 0) {
r =
(r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744)) /
int256(0x0000000000000000000000000043cbaf42a000812488fc5c220ad7b97bf6e99e); // * e ^ -32
}
// e ^ -16
if ((x & int256(0x0000000000000000000000000000000800000000000000000000000000000000)) != 0) {
r =
(r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f)) /
int256(0x000000000000000000000000000005d27a9f51c31b7c2f8038212a0574779991); // * e ^ -16
}
// e ^ -8
if ((x & int256(0x0000000000000000000000000000000400000000000000000000000000000000)) != 0) {
r =
(r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a)) /
int256(0x0000000000000000000000000000001b4c902e273a58678d6d3bfdb93db96d02); // * e ^ -8
}
// e ^ -4
if ((x & int256(0x0000000000000000000000000000000200000000000000000000000000000000)) != 0) {
r =
(r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571)) /
int256(0x00000000000000000000000000000003b1cc971a9bb5b9867477440d6d157750); // * e ^ -4
}
// e ^ -2
if ((x & int256(0x0000000000000000000000000000000100000000000000000000000000000000)) != 0) {
r =
(r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86)) /
int256(0x000000000000000000000000000000015bf0a8b1457695355fb8ac404e7a79e3); // * e ^ -2
}
// e ^ -1
if ((x & int256(0x0000000000000000000000000000000080000000000000000000000000000000)) != 0) {
r =
(r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3)) /
int256(0x00000000000000000000000000000000d3094c70f034de4b96ff7d5b6f99fcd8); // * e ^ -1
}
// e ^ -0.5
if ((x & int256(0x0000000000000000000000000000000040000000000000000000000000000000)) != 0) {
r =
(r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc)) /
int256(0x00000000000000000000000000000000a45af1e1f40c333b3de1db4dd55f29a7); // * e ^ -0.5
}
// e ^ -0.25
if ((x & int256(0x0000000000000000000000000000000020000000000000000000000000000000)) != 0) {
r =
(r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) /
int256(0x00000000000000000000000000000000910b022db7ae67ce76b441c27035c6a1); // * e ^ -0.25
}
// e ^ -0.125
if ((x & int256(0x0000000000000000000000000000000010000000000000000000000000000000)) != 0) {
r =
(r * int256(0x00000000000000000000000000000000783eafef1c0a8f3978c7f81824d62ebf)) /
int256(0x0000000000000000000000000000000088415abbe9a76bead8d00cf112e4d4a8); // * e ^ -0.125
}
}
/// @dev Returns the multiplication two numbers, reverting on overflow.
function _mul(int256 a, int256 b) private pure returns (int256 c) {
if (a == 0 || b == 0) {
return 0;
}
unchecked {
c = a * b;
if (c / a != b || c / b != a) {
revert("overflow");
}
}
}
/// @dev Returns the division of two numbers, reverting on division by zero.
function _div(int256 a, int256 b) private pure returns (int256 c) {
if (b == 0) {
revert("overflow");
}
if (a == MIN_FIXED_VAL && b == -1) {
revert("overflow");
}
unchecked {
c = a / b;
}
}
/// @dev Adds two numbers, reverting on overflow.
function _add(int256 a, int256 b) private pure returns (int256 c) {
unchecked {
c = a + b;
if ((a < 0 && b < 0 && c > a) || (a > 0 && b > 0 && c < a)) {
revert("overflow");
}
}
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
/**
* @title LinkedList library
* @notice A library to manage singly linked lists.
*
* The library makes no assumptions about the contents of the items, the only
* requirements on the items are:
* - they must be represented by a unique bytes32 id
* - the id of the item must not be bytes32(0)
* - each item must have a reference to the next item in the list
* - the list cannot have more than `MAX_ITEMS` items
*
* A contract using this library must store:
* - a LinkedList.List to keep track of the list metadata
* - a mapping from bytes32 to the item data
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
library LinkedList {
using LinkedList for ILinkedList.List;
/// @notice Empty bytes constant
bytes internal constant NULL_BYTES = bytes("");
/// @notice Maximum amount of items allowed in the list
uint256 internal constant MAX_ITEMS = 10_000;
/**
* @notice Adds an item to the list.
* The item is added to the end of the list.
* @dev Note that this function will not take care of linking the
* old tail to the new item. The caller should take care of this.
* It will also not ensure id uniqueness.
* @dev There is a maximum number of elements that can be added to the list.
* @param self The list metadata
* @param id The id of the item to add
*/
function addTail(ILinkedList.List storage self, bytes32 id) internal {
require(self.count < MAX_ITEMS, ILinkedList.LinkedListMaxElementsExceeded());
require(id != bytes32(0), ILinkedList.LinkedListInvalidZeroId());
self.tail = id;
self.nonce += 1;
if (self.count == 0) self.head = id;
self.count += 1;
}
/**
* @notice Removes an item from the list.
* The item is removed from the beginning of the list.
* @param self The list metadata
* @param getNextItem A function to get the next item in the list. It should take
* the id of the current item and return the id of the next item.
* @param deleteItem A function to delete an item. This should delete the item from
* the contract storage. It takes the id of the item to delete.
* @return The id of the head of the list.
*/
function removeHead(
ILinkedList.List storage self,
function(bytes32) view returns (bytes32) getNextItem,
function(bytes32) deleteItem
) internal returns (bytes32) {
require(self.count > 0, ILinkedList.LinkedListEmptyList());
bytes32 nextItem = getNextItem(self.head);
deleteItem(self.head);
self.count -= 1;
self.head = nextItem;
if (self.count == 0) self.tail = bytes32(0);
return self.head;
}
/**
* @notice Traverses the list and processes each item.
* It deletes the processed items from both the list and the storage mapping.
* @param self The list metadata
* @param getNextItem A function to get the next item in the list. It should take
* the id of the current item and return the id of the next item.
* @param processItem A function to process an item. The function should take the id of the item
* and an accumulator, and return:
* - a boolean indicating whether the traversal should stop
* - an accumulator to pass data between iterations
* @param deleteItem A function to delete an item. This should delete the item from
* the contract storage. It takes the id of the item to delete.
* @param processInitAcc The initial accumulator data
* @param iterations The maximum number of iterations to perform. If 0, the traversal will continue
* until the end of the list.
* @return The number of items processed
* @return The final accumulator data.
*/
function traverse(
ILinkedList.List storage self,
function(bytes32) view returns (bytes32) getNextItem,
function(bytes32, bytes memory) returns (bool, bytes memory) processItem,
function(bytes32) deleteItem,
bytes memory processInitAcc,
uint256 iterations
) internal returns (uint256, bytes memory) {
require(iterations <= self.count, ILinkedList.LinkedListInvalidIterations());
uint256 itemCount = 0;
iterations = (iterations == 0) ? self.count : iterations;
bytes32 cursor = self.head;
while (cursor != bytes32(0) && iterations > 0) {
(bool shouldBreak, bytes memory acc_) = processItem(cursor, processInitAcc);
if (shouldBreak) break;
processInitAcc = acc_;
cursor = self.removeHead(getNextItem, deleteItem);
iterations--;
itemCount++;
}
return (itemCount, processInitAcc);
}
}// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.27; /** * @title MathUtils Library * @notice A collection of functions to perform math operations * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ library MathUtils { /** * @dev Calculates the weighted average of two values pondering each of these * values based on configured weights. The contribution of each value N is * weightN/(weightA + weightB). The calculation rounds up to ensure the result * is always equal or greater than the smallest of the two values. * @param valueA The amount for value A * @param weightA The weight to use for value A * @param valueB The amount for value B * @param weightB The weight to use for value B */ function weightedAverageRoundingUp( uint256 valueA, uint256 weightA, uint256 valueB, uint256 weightB ) internal pure returns (uint256) { return ((valueA * weightA) + (valueB * weightB) + (weightA + weightB - 1)) / (weightA + weightB); } /** * @dev Returns the minimum of two numbers. * @param x The first number * @param y The second number * @return The minimum of the two numbers */ function min(uint256 x, uint256 y) internal pure returns (uint256) { return x <= y ? x : y; } /** * @dev Returns the difference between two numbers or zero if negative. * @param x The first number * @param y The second number * @return The difference between the two numbers or zero if negative */ function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { return (x > y) ? x - y : 0; } }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; /** * @title PPMMath library * @notice A library for handling calculations with parts per million (PPM) amounts. * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ library PPMMath { /// @notice Maximum value (100%) in parts per million (PPM). uint256 internal constant MAX_PPM = 1_000_000; /** * @notice Thrown when a value is expected to be in PPM but is not. * @param value The value that is not in PPM. */ error PPMMathInvalidPPM(uint256 value); /** * @notice Thrown when no value in a multiplication is in PPM. * @param a The first value in the multiplication. * @param b The second value in the multiplication. */ error PPMMathInvalidMulPPM(uint256 a, uint256 b); /** * @notice Multiplies two values, one of which must be in PPM. * @param a The first value. * @param b The second value. * @return The result of the multiplication. */ function mulPPM(uint256 a, uint256 b) internal pure returns (uint256) { require(isValidPPM(a) || isValidPPM(b), PPMMathInvalidMulPPM(a, b)); return (a * b) / MAX_PPM; } /** * @notice Multiplies two values, the second one must be in PPM, and rounds up the result. * @dev requirements: * - The second value must be in PPM. * @param a The first value. * @param b The second value. * @return The result of the multiplication. */ function mulPPMRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { require(isValidPPM(b), PPMMathInvalidPPM(b)); return a - mulPPM(a, MAX_PPM - b); } /** * @notice Checks if a value is in PPM. * @dev A valid PPM value is between 0 and MAX_PPM. * @param value The value to check. * @return true if the value is in PPM, false otherwise. */ function isValidPPM(uint256 value) internal pure returns (bool) { return value <= MAX_PPM; } }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; /** * @title UintRange library * @notice A library for handling range checks on uint256 values. * @custom:security-contact Please email [email protected] if you find any * bugs. We may have an active bug bounty program. */ library UintRange { /** * @notice Checks if a value is in the range [`min`, `max`]. * @param value The value to check. * @param min The minimum value of the range. * @param max The maximum value of the range. * @return true if the value is in the range, false otherwise. */ function isInRange(uint256 value, uint256 min, uint256 max) internal pure returns (bool) { return value >= min && value <= max; } }
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IController } from "@graphprotocol/interfaces/contracts/contracts/governance/IController.sol";
import { IManaged } from "@graphprotocol/interfaces/contracts/contracts/governance/IManaged.sol";
/**
* @title Graph Controller contract (mock)
* @dev Controller is a registry of contracts for convenience. Inspired by Livepeer:
* https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol
*/
contract ControllerMock is IController {
/// @dev Track contract ids to contract proxy address
mapping(bytes32 contractName => address contractAddress) private _registry;
address public governor;
bool internal _paused;
bool internal _partialPaused;
address internal _pauseGuardian;
/// Emitted when the proxy address for a protocol contract has been set
event SetContractProxy(bytes32 indexed id, address contractAddress);
/**
* Constructor for the Controller mock
* @param governor_ Address of the governor
*/
constructor(address governor_) {
governor = governor_;
}
// -- Registry --
/**
* @notice Register contract id and mapped address
* @param id Contract id (keccak256 hash of contract name)
* @param contractAddress Contract address
*/
function setContractProxy(bytes32 id, address contractAddress) external override {
require(contractAddress != address(0), "Contract address must be set");
_registry[id] = contractAddress;
emit SetContractProxy(id, contractAddress);
}
/**
* @notice Unregister a contract address
* @param id Contract id (keccak256 hash of contract name)
*/
function unsetContractProxy(bytes32 id) external override {
_registry[id] = address(0);
emit SetContractProxy(id, address(0));
}
/**
* @notice Update a contract's controller
* @param id Contract id (keccak256 hash of contract name)
* @param controller New Controller address
*/
function updateController(bytes32 id, address controller) external override {
require(controller != address(0), "Controller must be set");
return IManaged(_registry[id]).setController(controller);
}
// -- Pausing --
/**
* @notice Change the partial paused state of the contract
* Partial pause is intended as a partial pause of the protocol
* @param toPause True if the contracts should be (partially) paused, false otherwise
*/
function setPartialPaused(bool toPause) external override {
_partialPaused = toPause;
}
/**
* @notice Change the paused state of the contract
* Full pause most of protocol functions
* @param toPause True if the contracts should be paused, false otherwise
*/
function setPaused(bool toPause) external override {
_paused = toPause;
}
/**
* @notice Change the Pause Guardian
* @param newPauseGuardian The address of the new Pause Guardian
*/
function setPauseGuardian(address newPauseGuardian) external override {
require(newPauseGuardian != address(0), "PauseGuardian must be set");
_pauseGuardian = newPauseGuardian;
}
/**
* @notice Getter to access governor
* @return Address of the governor
*/
function getGovernor() external view override returns (address) {
return governor;
}
/**
* @notice Get contract proxy address by its id
* @param id Contract id (keccak256 hash of contract name)
* @return Address of the proxy contract for the provided id
*/
function getContractProxy(bytes32 id) external view virtual override returns (address) {
return _registry[id];
}
/**
* @notice Getter to access paused
* @return True if the contracts are paused, false otherwise
*/
function paused() external view override returns (bool) {
return _paused;
}
/**
* @notice Getter to access partial pause status
* @return True if the contracts are partially paused, false otherwise
*/
function partialPaused() external view override returns (bool) {
return _partialPaused;
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
contract CurationMock {
mapping(bytes32 subgraphDeploymentID => uint256 tokens) public curation;
function signal(bytes32 subgraphDeploymentID, uint256 tokens) public {
curation[subgraphDeploymentID] += tokens;
}
function isCurated(bytes32 subgraphDeploymentID) public view returns (bool) {
return curation[subgraphDeploymentID] != 0;
}
function collect(bytes32 subgraphDeploymentID, uint256 tokens) external {
curation[subgraphDeploymentID] += tokens;
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
contract Dummy {}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IEpochManager } from "@graphprotocol/interfaces/contracts/contracts/epochs/IEpochManager.sol";
contract EpochManagerMock is IEpochManager {
// -- Variables --
uint256 public epochLength;
uint256 public lastRunEpoch;
uint256 public lastLengthUpdateEpoch;
uint256 public lastLengthUpdateBlock;
// -- Configuration --
function setEpochLength(uint256 epochLength_) public {
lastLengthUpdateEpoch = 1;
lastLengthUpdateBlock = blockNum();
epochLength = epochLength_;
}
// -- Epochs
function runEpoch() public {
lastRunEpoch = currentEpoch();
}
// -- Getters --
function isCurrentEpochRun() public view returns (bool) {
return lastRunEpoch == currentEpoch();
}
function blockNum() public view returns (uint256) {
return block.number;
}
function blockHash(uint256 block_) public view returns (bytes32) {
return blockhash(block_);
}
function currentEpoch() public view returns (uint256) {
return lastLengthUpdateEpoch + epochsSinceUpdate();
}
function currentEpochBlock() public view returns (uint256) {
return lastLengthUpdateBlock + (epochsSinceUpdate() * epochLength);
}
function currentEpochBlockSinceStart() public view returns (uint256) {
return blockNum() - currentEpochBlock();
}
function epochsSince(uint256 epoch_) public view returns (uint256) {
uint256 epoch = currentEpoch();
return epoch_ < epoch ? (epoch - epoch_) : 0;
}
function epochsSinceUpdate() public view returns (uint256) {
return (blockNum() - lastLengthUpdateBlock) / epochLength;
}
}// solhint-disable no-global-import // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.7.6 || 0.8.27; // We import these here to force Hardhat to compile them. // This ensures that their artifacts are available for Hardhat Ignition to use. import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
contract MockGRTToken is ERC20, IGraphToken {
constructor() ERC20("Graph Token", "GRT") {}
function burn(uint256 tokens) external {
_burn(msg.sender, tokens);
}
function burnFrom(address from, uint256 tokens) external {
_burn(from, tokens);
}
// -- Mint Admin --
function addMinter(address account) external {}
function removeMinter(address account) external {}
function renounceMinter() external {}
// -- Permit --
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {}
// -- Allowance --
function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {}
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {}
function isMinter(address account) external view returns (bool) {}
function mint(address to, uint256 tokens) public {
_mint(to, tokens);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { MockGRTToken } from "./MockGRTToken.sol";
contract RewardsManagerMock {
// -- Variables --
MockGRTToken public token;
uint256 private _rewards;
// -- Constructor --
constructor(MockGRTToken token_, uint256 rewards) {
token = token_;
_rewards = rewards;
}
function takeRewards(address) external returns (uint256) {
token.mint(msg.sender, _rewards);
return _rewards;
}
function onSubgraphAllocationUpdate(bytes32) public returns (uint256) {}
function onSubgraphSignalUpdate(bytes32 subgraphDeploymentID) external returns (uint256) {}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { IGraphTallyCollector } from "@graphprotocol/interfaces/contracts/horizon/IGraphTallyCollector.sol";
import { IPaymentsCollector } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsCollector.sol";
import { Authorizable } from "../../utilities/Authorizable.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { PPMMath } from "../../libraries/PPMMath.sol";
import { GraphDirectory } from "../../utilities/GraphDirectory.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title GraphTallyCollector contract
* @dev Implements the {IGraphTallyCollector}, {IPaymentCollector} and {IAuthorizable} interfaces.
* @notice A payments collector contract that can be used to collect payments using a GraphTally RAV (Receipt Aggregate Voucher).
* @dev Note that the contract expects the RAV aggregate value to be monotonically increasing, each successive RAV for the same
* (data service-payer-receiver) tuple should have a value greater than the previous one. The contract will keep track of the tokens
* already collected and calculate the difference to collect.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTallyCollector {
using PPMMath for uint256;
/// @notice The EIP712 typehash for the ReceiptAggregateVoucher struct
bytes32 private constant EIP712_RAV_TYPEHASH =
keccak256(
"ReceiptAggregateVoucher(bytes32 collectionId,address payer,address serviceProvider,address dataService,uint64 timestampNs,uint128 valueAggregate,bytes metadata)"
);
/// @notice Tracks the amount of tokens already collected by a data service from a payer to a receiver.
/// @dev The collectionId provides a secondary key for grouping payment tracking if needed. Data services that do not require
/// grouping can use the same collectionId for all payments (0x00 or some other default value).
mapping(address dataService => mapping(bytes32 collectionId => mapping(address receiver => mapping(address payer => uint256 tokens))))
public tokensCollected;
/**
* @notice Constructs a new instance of the GraphTallyCollector contract.
* @param eip712Name The name of the EIP712 domain.
* @param eip712Version The version of the EIP712 domain.
* @param controller The address of the Graph controller.
* @param revokeSignerThawingPeriod The duration (in seconds) in which a signer is thawing before they can be revoked.
*/
constructor(
string memory eip712Name,
string memory eip712Version,
address controller,
uint256 revokeSignerThawingPeriod
) EIP712(eip712Name, eip712Version) GraphDirectory(controller) Authorizable(revokeSignerThawingPeriod) {}
/**
* @notice See {IGraphPayments.collect}.
* @dev Requirements:
* - Caller must be the data service the RAV was issued to.
* - Signer of the RAV must be authorized to sign for the payer.
* - Service provider must have an active provision with the data service to collect payments.
* @notice REVERT: This function may revert if ECDSA.recover fails, check ECDSA library for details.
* @param paymentType The payment type to collect
* @param data Additional data required for the payment collection. Encoded as follows:
* - SignedRAV `signedRAV`: The signed RAV
* - uint256 `dataServiceCut`: The data service cut in PPM
* - address `receiverDestination`: The address where the receiver's payment should be sent.
* @return The amount of tokens collected
*/
/// @inheritdoc IPaymentsCollector
function collect(IGraphPayments.PaymentTypes paymentType, bytes calldata data) external override returns (uint256) {
return _collect(paymentType, data, 0);
}
/// @inheritdoc IGraphTallyCollector
function collect(
IGraphPayments.PaymentTypes paymentType,
bytes calldata data,
uint256 tokensToCollect
) external override returns (uint256) {
return _collect(paymentType, data, tokensToCollect);
}
/// @inheritdoc IGraphTallyCollector
function recoverRAVSigner(SignedRAV calldata signedRAV) external view override returns (address) {
return _recoverRAVSigner(signedRAV);
}
/// @inheritdoc IGraphTallyCollector
function encodeRAV(ReceiptAggregateVoucher calldata rav) external view returns (bytes32) {
return _encodeRAV(rav);
}
/**
* @notice See {IPaymentsCollector.collect}
* This variant adds the ability to partially collect a RAV by specifying the amount of tokens to collect.
* @param _paymentType The payment type to collect
* @param _data Additional data required for the payment collection
* @param _tokensToCollect The amount of tokens to collect. If 0, all tokens from the RAV will be collected.
* @return The amount of tokens collected
*/
function _collect(
IGraphPayments.PaymentTypes _paymentType,
bytes calldata _data,
uint256 _tokensToCollect
) private returns (uint256) {
(SignedRAV memory signedRAV, uint256 dataServiceCut, address receiverDestination) = abi.decode(
_data,
(SignedRAV, uint256, address)
);
// Ensure caller is the RAV data service
require(
signedRAV.rav.dataService == msg.sender,
GraphTallyCollectorCallerNotDataService(msg.sender, signedRAV.rav.dataService)
);
// Ensure RAV signer is authorized for the payer
_requireAuthorizedSigner(signedRAV);
bytes32 collectionId = signedRAV.rav.collectionId;
address dataService = signedRAV.rav.dataService;
address receiver = signedRAV.rav.serviceProvider;
// Check the service provider has an active provision with the data service
// This prevents an attack where the payer can deny the service provider from collecting payments
// by using a signer as data service to syphon off the tokens in the escrow to an account they control
{
uint256 tokensAvailable = _graphStaking().getProviderTokensAvailable(
signedRAV.rav.serviceProvider,
signedRAV.rav.dataService
);
require(tokensAvailable > 0, GraphTallyCollectorUnauthorizedDataService(signedRAV.rav.dataService));
}
uint256 tokensToCollect = 0;
{
uint256 tokensRAV = signedRAV.rav.valueAggregate;
uint256 tokensAlreadyCollected = tokensCollected[dataService][collectionId][receiver][signedRAV.rav.payer];
require(
tokensRAV > tokensAlreadyCollected,
GraphTallyCollectorInconsistentRAVTokens(tokensRAV, tokensAlreadyCollected)
);
if (_tokensToCollect == 0) {
tokensToCollect = tokensRAV - tokensAlreadyCollected;
} else {
require(
_tokensToCollect <= tokensRAV - tokensAlreadyCollected,
GraphTallyCollectorInvalidTokensToCollectAmount(
_tokensToCollect,
tokensRAV - tokensAlreadyCollected
)
);
tokensToCollect = _tokensToCollect;
}
}
if (tokensToCollect > 0) {
tokensCollected[dataService][collectionId][receiver][signedRAV.rav.payer] += tokensToCollect;
_graphPaymentsEscrow().collect(
_paymentType,
signedRAV.rav.payer,
receiver,
tokensToCollect,
dataService,
dataServiceCut,
receiverDestination
);
}
emit PaymentCollected(_paymentType, collectionId, signedRAV.rav.payer, receiver, dataService, tokensToCollect);
// This event is emitted to allow reconstructing RAV history with onchain data.
emit RAVCollected(
collectionId,
signedRAV.rav.payer,
receiver,
dataService,
signedRAV.rav.timestampNs,
signedRAV.rav.valueAggregate,
signedRAV.rav.metadata,
signedRAV.signature
);
return tokensToCollect;
}
/**
* @dev Recovers the signer address of a signed ReceiptAggregateVoucher (RAV).
* @param _signedRAV The SignedRAV containing the RAV and its signature.
* @return The address of the signer.
*/
function _recoverRAVSigner(SignedRAV memory _signedRAV) private view returns (address) {
bytes32 messageHash = _encodeRAV(_signedRAV.rav);
return ECDSA.recover(messageHash, _signedRAV.signature);
}
/**
* @dev Computes the hash of a ReceiptAggregateVoucher (RAV).
* @param _rav The RAV for which to compute the hash.
* @return The hash of the RAV.
*/
function _encodeRAV(ReceiptAggregateVoucher memory _rav) private view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
EIP712_RAV_TYPEHASH,
_rav.collectionId,
_rav.payer,
_rav.serviceProvider,
_rav.dataService,
_rav.timestampNs,
_rav.valueAggregate,
keccak256(_rav.metadata)
)
)
);
}
/**
* @notice Reverts if the RAV signer is not authorized by the payer
* @param _signedRAV The signed RAV
*/
function _requireAuthorizedSigner(SignedRAV memory _signedRAV) private view {
require(
_isAuthorized(_signedRAV.rav.payer, _recoverRAVSigner(_signedRAV)),
GraphTallyCollectorInvalidRAVSigner()
);
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import { TokenUtils } from "@graphprotocol/contracts/contracts/utils/TokenUtils.sol";
import { PPMMath } from "../libraries/PPMMath.sol";
import { GraphDirectory } from "../utilities/GraphDirectory.sol";
/**
* @title GraphPayments contract
* @notice This contract is part of the Graph Horizon payments protocol. It's designed
* to pull funds (GRT) from the {PaymentsEscrow} and distribute them according to a
* set of pre established rules.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract GraphPayments is Initializable, MulticallUpgradeable, GraphDirectory, IGraphPayments {
using TokenUtils for IGraphToken;
using PPMMath for uint256;
/// @notice Protocol payment cut in PPM
uint256 public immutable PROTOCOL_PAYMENT_CUT;
/**
* @notice Constructor for the {GraphPayments} contract
* @dev This contract is upgradeable however we still use the constructor to set
* a few immutable variables.
* @param controller The address of the Graph controller
* @param protocolPaymentCut The protocol tax in PPM
*/
constructor(address controller, uint256 protocolPaymentCut) GraphDirectory(controller) {
require(PPMMath.isValidPPM(protocolPaymentCut), GraphPaymentsInvalidCut(protocolPaymentCut));
PROTOCOL_PAYMENT_CUT = protocolPaymentCut;
_disableInitializers();
}
/// @inheritdoc IGraphPayments
function initialize() external initializer {
__Multicall_init();
}
/// @inheritdoc IGraphPayments
function collect(
IGraphPayments.PaymentTypes paymentType,
address receiver,
uint256 tokens,
address dataService,
uint256 dataServiceCut,
address receiverDestination
) external {
require(PPMMath.isValidPPM(dataServiceCut), GraphPaymentsInvalidCut(dataServiceCut));
// Pull tokens from the sender
_graphToken().pullTokens(msg.sender, tokens);
// Calculate token amounts for each party
// Order matters: protocol -> data service -> delegators -> receiver
// Note the substractions should not underflow as we are only deducting a percentage of the remainder
uint256 tokensRemaining = tokens;
uint256 tokensProtocol = tokensRemaining.mulPPMRoundUp(PROTOCOL_PAYMENT_CUT);
tokensRemaining = tokensRemaining - tokensProtocol;
uint256 tokensDataService = tokensRemaining.mulPPMRoundUp(dataServiceCut);
tokensRemaining = tokensRemaining - tokensDataService;
uint256 tokensDelegationPool = 0;
IHorizonStakingTypes.DelegationPool memory pool = _graphStaking().getDelegationPool(receiver, dataService);
if (pool.shares > 0) {
tokensDelegationPool = tokensRemaining.mulPPMRoundUp(
_graphStaking().getDelegationFeeCut(receiver, dataService, paymentType)
);
tokensRemaining = tokensRemaining - tokensDelegationPool;
}
// Pay all parties
_graphToken().burnTokens(tokensProtocol);
_graphToken().pushTokens(dataService, tokensDataService);
if (tokensDelegationPool > 0) {
_graphToken().approve(address(_graphStaking()), tokensDelegationPool);
_graphStaking().addToDelegationPool(receiver, dataService, tokensDelegationPool);
}
if (tokensRemaining > 0) {
if (receiverDestination == address(0)) {
_graphToken().approve(address(_graphStaking()), tokensRemaining);
_graphStaking().stakeTo(receiver, tokensRemaining);
} else {
_graphToken().pushTokens(receiverDestination, tokensRemaining);
}
}
emit GraphPaymentCollected(
paymentType,
msg.sender,
receiver,
dataService,
tokens,
tokensProtocol,
tokensDataService,
tokensDelegationPool,
tokensRemaining,
receiverDestination
);
}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import { TokenUtils } from "@graphprotocol/contracts/contracts/utils/TokenUtils.sol";
import { GraphDirectory } from "../utilities/GraphDirectory.sol";
/**
* @title PaymentsEscrow contract
* @dev Implements the {IPaymentsEscrow} interface
* @notice This contract is part of the Graph Horizon payments protocol. It holds the funds (GRT)
* for payments made through the payments protocol for services provided
* via a Graph Horizon data service.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, IPaymentsEscrow {
using TokenUtils for IGraphToken;
/// @notice The maximum thawing period (in seconds) for both escrow withdrawal and collector revocation
/// @dev This is a precautionary measure to avoid inadvertedly locking funds for too long
uint256 public constant MAX_WAIT_PERIOD = 90 days;
/// @notice Thawing period in seconds for escrow funds withdrawal
uint256 public immutable WITHDRAW_ESCROW_THAWING_PERIOD;
/// @notice Escrow account details for payer-collector-receiver tuples
mapping(address payer => mapping(address collector => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount)))
public escrowAccounts;
/**
* @notice Modifier to prevent function execution when contract is paused
* @dev Reverts if the controller indicates the contract is paused
*/
modifier notPaused() {
require(!_graphController().paused(), PaymentsEscrowIsPaused());
_;
}
/**
* @notice Construct the PaymentsEscrow contract
* @param controller The address of the controller
* @param withdrawEscrowThawingPeriod Thawing period in seconds for escrow funds withdrawal
*/
constructor(address controller, uint256 withdrawEscrowThawingPeriod) GraphDirectory(controller) {
require(
withdrawEscrowThawingPeriod <= MAX_WAIT_PERIOD,
PaymentsEscrowThawingPeriodTooLong(withdrawEscrowThawingPeriod, MAX_WAIT_PERIOD)
);
WITHDRAW_ESCROW_THAWING_PERIOD = withdrawEscrowThawingPeriod;
_disableInitializers();
}
/// @inheritdoc IPaymentsEscrow
function initialize() external initializer {
__Multicall_init();
}
/// @inheritdoc IPaymentsEscrow
function deposit(address collector, address receiver, uint256 tokens) external override notPaused {
_deposit(msg.sender, collector, receiver, tokens);
}
/// @inheritdoc IPaymentsEscrow
function depositTo(address payer, address collector, address receiver, uint256 tokens) external override notPaused {
_deposit(payer, collector, receiver, tokens);
}
/// @inheritdoc IPaymentsEscrow
function thaw(address collector, address receiver, uint256 tokens) external override notPaused {
require(tokens > 0, PaymentsEscrowInvalidZeroTokens());
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
require(account.balance >= tokens, PaymentsEscrowInsufficientBalance(account.balance, tokens));
account.tokensThawing = tokens;
account.thawEndTimestamp = block.timestamp + WITHDRAW_ESCROW_THAWING_PERIOD;
emit Thaw(msg.sender, collector, receiver, tokens, account.thawEndTimestamp);
}
/// @inheritdoc IPaymentsEscrow
function cancelThaw(address collector, address receiver) external override notPaused {
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
require(account.tokensThawing != 0, PaymentsEscrowNotThawing());
uint256 tokensThawing = account.tokensThawing;
uint256 thawEndTimestamp = account.thawEndTimestamp;
account.tokensThawing = 0;
account.thawEndTimestamp = 0;
emit CancelThaw(msg.sender, collector, receiver, tokensThawing, thawEndTimestamp);
}
/// @inheritdoc IPaymentsEscrow
function withdraw(address collector, address receiver) external override notPaused {
EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver];
require(account.thawEndTimestamp != 0, PaymentsEscrowNotThawing());
require(
account.thawEndTimestamp < block.timestamp,
PaymentsEscrowStillThawing(block.timestamp, account.thawEndTimestamp)
);
// Amount is the minimum between the amount being thawed and the actual balance
uint256 tokens = account.tokensThawing > account.balance ? account.balance : account.tokensThawing;
account.balance -= tokens;
account.tokensThawing = 0;
account.thawEndTimestamp = 0;
_graphToken().pushTokens(msg.sender, tokens);
emit Withdraw(msg.sender, collector, receiver, tokens);
}
/// @inheritdoc IPaymentsEscrow
function collect(
IGraphPayments.PaymentTypes paymentType,
address payer,
address receiver,
uint256 tokens,
address dataService,
uint256 dataServiceCut,
address receiverDestination
) external override notPaused {
// Check if there are enough funds in the escrow account
EscrowAccount storage account = escrowAccounts[payer][msg.sender][receiver];
require(account.balance >= tokens, PaymentsEscrowInsufficientBalance(account.balance, tokens));
// Reduce amount from account balance
account.balance -= tokens;
uint256 escrowBalanceBefore = _graphToken().balanceOf(address(this));
_graphToken().approve(address(_graphPayments()), tokens);
_graphPayments().collect(paymentType, receiver, tokens, dataService, dataServiceCut, receiverDestination);
// Verify that the escrow balance is consistent with the collected tokens
uint256 escrowBalanceAfter = _graphToken().balanceOf(address(this));
require(
escrowBalanceBefore == tokens + escrowBalanceAfter,
PaymentsEscrowInconsistentCollection(escrowBalanceBefore, escrowBalanceAfter, tokens)
);
emit EscrowCollected(paymentType, payer, msg.sender, receiver, tokens, receiverDestination);
}
/// @inheritdoc IPaymentsEscrow
function getBalance(address payer, address collector, address receiver) external view override returns (uint256) {
EscrowAccount storage account = escrowAccounts[payer][collector][receiver];
return account.balance > account.tokensThawing ? account.balance - account.tokensThawing : 0;
}
/**
* @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where
* the payer is the transaction caller.
* @param _payer The address of the payer
* @param _collector The address of the collector
* @param _receiver The address of the receiver
* @param _tokens The amount of tokens to deposit
*/
function _deposit(address _payer, address _collector, address _receiver, uint256 _tokens) private {
escrowAccounts[_payer][_collector][_receiver].balance += _tokens;
_graphToken().pullTokens(msg.sender, _tokens);
emit Deposit(_payer, _collector, _receiver, _tokens);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IHorizonStakingMain } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol";
import { IHorizonStakingExtension } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
import { TokenUtils } from "@graphprotocol/contracts/contracts/utils/TokenUtils.sol";
import { MathUtils } from "../libraries/MathUtils.sol";
import { PPMMath } from "../libraries/PPMMath.sol";
import { LinkedList } from "../libraries/LinkedList.sol";
import { HorizonStakingBase } from "./HorizonStakingBase.sol";
/**
* @title HorizonStaking contract
* @notice The {HorizonStaking} contract allows service providers to stake and provision tokens to verifiers to be used
* as economic security for a service. It also allows delegators to delegate towards a service provider provision.
* @dev Implements the {IHorizonStakingMain} interface.
* @dev This is the main Staking contract in The Graph protocol after the Horizon upgrade.
* It is designed to be deployed as an upgrade to the L2Staking contract from the legacy contracts package.
* @dev It uses a {HorizonStakingExtension} contract to implement the full {IHorizonStaking} interface through delegatecalls.
* This is due to the contract size limit on Arbitrum (24kB). The extension contract implements functionality to support
* the legacy staking functions. It can be eventually removed without affecting the main staking contract.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain {
using TokenUtils for IGraphToken;
using PPMMath for uint256;
using LinkedList for ILinkedList.List;
/// @dev Maximum number of simultaneous stake thaw requests (per provision) or undelegations (per delegation)
uint256 private constant MAX_THAW_REQUESTS = 1_000;
/// @dev Address of the staking extension contract
address private immutable STAKING_EXTENSION_ADDRESS;
/// @dev Minimum amount of delegation.
uint256 private constant MIN_DELEGATION = 1e18;
/**
* @notice Checks that the caller is authorized to operate over a provision.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
*/
modifier onlyAuthorized(address serviceProvider, address verifier) {
require(
_isAuthorized(serviceProvider, verifier, msg.sender),
HorizonStakingNotAuthorized(serviceProvider, verifier, msg.sender)
);
_;
}
/**
* @notice Checks that the caller is authorized to operate over a provision or it is the verifier.
* @param serviceProvider The address of the service provider.
* @param verifier The address of the verifier.
*/
modifier onlyAuthorizedOrVerifier(address serviceProvider, address verifier) {
require(
_isAuthorized(serviceProvider, verifier, msg.sender) || msg.sender == verifier,
HorizonStakingNotAuthorized(serviceProvider, verifier, msg.sender)
);
_;
}
/**
* @dev The staking contract is upgradeable however we still use the constructor to set
* a few immutable variables.
* @param controller The address of the Graph controller contract.
* @param stakingExtensionAddress The address of the staking extension contract.
* @param subgraphDataServiceAddress The address of the subgraph data service.
*/
constructor(
address controller,
address stakingExtensionAddress,
address subgraphDataServiceAddress
) HorizonStakingBase(controller, subgraphDataServiceAddress) {
STAKING_EXTENSION_ADDRESS = stakingExtensionAddress;
}
/**
* @notice Delegates the current call to the StakingExtension implementation.
* @dev This function does not return to its internal call site, it will return directly to the
* external caller.
*/
// solhint-disable-next-line payable-fallback, no-complex-fallback
fallback() external {
address extensionImpl = STAKING_EXTENSION_ADDRESS;
// solhint-disable-next-line no-inline-assembly
assembly {
// (a) get free memory pointer
let ptr := mload(0x40)
// (1) copy incoming call data
calldatacopy(ptr, 0, calldatasize())
// (2) forward call to logic contract
let result := delegatecall(gas(), extensionImpl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
// (3) retrieve return data
returndatacopy(ptr, 0, size)
// (4) forward return data back to caller
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
/*
* STAKING
*/
/// @inheritdoc IHorizonStakingMain
function stake(uint256 tokens) external override notPaused {
_stakeTo(msg.sender, tokens);
}
/// @inheritdoc IHorizonStakingMain
function stakeTo(address serviceProvider, uint256 tokens) external override notPaused {
_stakeTo(serviceProvider, tokens);
}
/// @inheritdoc IHorizonStakingMain
function stakeToProvision(
address serviceProvider,
address verifier,
uint256 tokens
) external override notPaused onlyAuthorizedOrVerifier(serviceProvider, verifier) {
_stakeTo(serviceProvider, tokens);
_addToProvision(serviceProvider, verifier, tokens);
}
/// @inheritdoc IHorizonStakingMain
function unstake(uint256 tokens) external override notPaused {
_unstake(tokens);
}
/// @inheritdoc IHorizonStakingMain
function withdraw() external override notPaused {
_withdraw(msg.sender);
}
/*
* PROVISIONS
*/
/// @inheritdoc IHorizonStakingMain
function provision(
address serviceProvider,
address verifier,
uint256 tokens,
uint32 maxVerifierCut,
uint64 thawingPeriod
) external override notPaused onlyAuthorized(serviceProvider, verifier) {
_createProvision(serviceProvider, tokens, verifier, maxVerifierCut, thawingPeriod);
}
/// @inheritdoc IHorizonStakingMain
function addToProvision(
address serviceProvider,
address verifier,
uint256 tokens
) external override notPaused onlyAuthorized(serviceProvider, verifier) {
_addToProvision(serviceProvider, verifier, tokens);
}
/// @inheritdoc IHorizonStakingMain
function thaw(
address serviceProvider,
address verifier,
uint256 tokens
) external override notPaused onlyAuthorized(serviceProvider, verifier) returns (bytes32) {
return _thaw(serviceProvider, verifier, tokens);
}
/// @inheritdoc IHorizonStakingMain
function deprovision(
address serviceProvider,
address verifier,
uint256 nThawRequests
) external override onlyAuthorized(serviceProvider, verifier) notPaused {
_deprovision(serviceProvider, verifier, nThawRequests);
}
/// @inheritdoc IHorizonStakingMain
function reprovision(
address serviceProvider,
address oldVerifier,
address newVerifier,
uint256 nThawRequests
)
external
override
notPaused
onlyAuthorized(serviceProvider, oldVerifier)
onlyAuthorized(serviceProvider, newVerifier)
{
uint256 tokensThawed = _deprovision(serviceProvider, oldVerifier, nThawRequests);
_addToProvision(serviceProvider, newVerifier, tokensThawed);
}
/// @inheritdoc IHorizonStakingMain
function setProvisionParameters(
address serviceProvider,
address verifier,
uint32 newMaxVerifierCut,
uint64 newThawingPeriod
) external override notPaused onlyAuthorized(serviceProvider, verifier) {
// Provision must exist
Provision storage prov = _provisions[serviceProvider][verifier];
require(prov.createdAt != 0, HorizonStakingInvalidProvision(serviceProvider, verifier));
bool verifierCutChanged = prov.maxVerifierCutPending != newMaxVerifierCut;
bool thawingPeriodChanged = prov.thawingPeriodPending != newThawingPeriod;
if (verifierCutChanged || thawingPeriodChanged) {
if (verifierCutChanged) {
require(PPMMath.isValidPPM(newMaxVerifierCut), HorizonStakingInvalidMaxVerifierCut(newMaxVerifierCut));
prov.maxVerifierCutPending = newMaxVerifierCut;
}
if (thawingPeriodChanged) {
require(
newThawingPeriod <= _maxThawingPeriod,
HorizonStakingInvalidThawingPeriod(newThawingPeriod, _maxThawingPeriod)
);
prov.thawingPeriodPending = newThawingPeriod;
}
prov.lastParametersStagedAt = block.timestamp;
emit ProvisionParametersStaged(serviceProvider, verifier, newMaxVerifierCut, newThawingPeriod);
}
}
/// @inheritdoc IHorizonStakingMain
function acceptProvisionParameters(address serviceProvider) external override notPaused {
address verifier = msg.sender;
// Provision must exist
Provision storage prov = _provisions[serviceProvider][verifier];
require(prov.createdAt != 0, HorizonStakingInvalidProvision(serviceProvider, verifier));
if ((prov.maxVerifierCutPending != prov.maxVerifierCut) || (prov.thawingPeriodPending != prov.thawingPeriod)) {
prov.maxVerifierCut = prov.maxVerifierCutPending;
prov.thawingPeriod = prov.thawingPeriodPending;
emit ProvisionParametersSet(serviceProvider, verifier, prov.maxVerifierCut, prov.thawingPeriod);
}
}
/*
* DELEGATION
*/
/// @inheritdoc IHorizonStakingMain
function delegate(
address serviceProvider,
address verifier,
uint256 tokens,
uint256 minSharesOut
) external override notPaused {
require(tokens != 0, HorizonStakingInvalidZeroTokens());
_graphToken().pullTokens(msg.sender, tokens);
_delegate(serviceProvider, verifier, tokens, minSharesOut);
}
/// @inheritdoc IHorizonStakingMain
function addToDelegationPool(
address serviceProvider,
address verifier,
uint256 tokens
) external override notPaused {
require(tokens != 0, HorizonStakingInvalidZeroTokens());
// Provision must exist before adding to delegation pool
Provision memory prov = _provisions[serviceProvider][verifier];
require(prov.createdAt != 0, HorizonStakingInvalidProvision(serviceProvider, verifier));
// Delegation pool must exist before adding tokens
DelegationPoolInternal storage pool = _getDelegationPool(serviceProvider, verifier);
require(pool.shares > 0, HorizonStakingInvalidDelegationPool(serviceProvider, verifier));
pool.tokens = pool.tokens + tokens;
_graphToken().pullTokens(msg.sender, tokens);
emit TokensToDelegationPoolAdded(serviceProvider, verifier, tokens);
}
/// @inheritdoc IHorizonStakingMain
function undelegate(
address serviceProvider,
address verifier,
uint256 shares
) external override notPaused returns (bytes32) {
return _undelegate(serviceProvider, verifier, shares);
}
/// @inheritdoc IHorizonStakingMain
function withdrawDelegated(
address serviceProvider,
address verifier,
uint256 nThawRequests
) external override notPaused {
_withdrawDelegated(serviceProvider, verifier, address(0), address(0), 0, nThawRequests);
}
/// @inheritdoc IHorizonStakingMain
function redelegate(
address oldServiceProvider,
address oldVerifier,
address newServiceProvider,
address newVerifier,
uint256 minSharesForNewProvider,
uint256 nThawRequests
) external override notPaused {
require(newServiceProvider != address(0), HorizonStakingInvalidServiceProviderZeroAddress());
require(newVerifier != address(0), HorizonStakingInvalidVerifierZeroAddress());
_withdrawDelegated(
oldServiceProvider,
oldVerifier,
newServiceProvider,
newVerifier,
minSharesForNewProvider,
nThawRequests
);
}
/// @inheritdoc IHorizonStakingMain
function setDelegationFeeCut(
address serviceProvider,
address verifier,
IGraphPayments.PaymentTypes paymentType,
uint256 feeCut
) external override notPaused onlyAuthorized(serviceProvider, verifier) {
require(PPMMath.isValidPPM(feeCut), HorizonStakingInvalidDelegationFeeCut(feeCut));
_delegationFeeCut[serviceProvider][verifier][paymentType] = feeCut;
emit DelegationFeeCutSet(serviceProvider, verifier, paymentType, feeCut);
}
/// @inheritdoc IHorizonStakingMain
function delegate(address serviceProvider, uint256 tokens) external override notPaused {
require(tokens != 0, HorizonStakingInvalidZeroTokens());
_graphToken().pullTokens(msg.sender, tokens);
_delegate(serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, tokens, 0);
}
/// @inheritdoc IHorizonStakingMain
function undelegate(address serviceProvider, uint256 shares) external override notPaused {
_undelegate(serviceProvider, SUBGRAPH_DATA_SERVICE_ADDRESS, shares);
}
/// @inheritdoc IHorizonStakingMain
function withdrawDelegated(
address serviceProvider,
address // deprecated - kept for backwards compatibility
) external override notPaused returns (uint256) {
// Get the delegation pool of the indexer
address delegator = msg.sender;
DelegationPoolInternal storage pool = _legacyDelegationPools[serviceProvider];
DelegationInternal storage delegation = pool.delegators[delegator];
// Validation
uint256 tokensToWithdraw = 0;
uint256 currentEpoch = _graphEpochManager().currentEpoch();
if (
delegation.__DEPRECATED_tokensLockedUntil > 0 && currentEpoch >= delegation.__DEPRECATED_tokensLockedUntil
) {
tokensToWithdraw = delegation.__DEPRECATED_tokensLocked;
}
require(tokensToWithdraw > 0, HorizonStakingNothingToWithdraw());
// Reset lock
delegation.__DEPRECATED_tokensLocked = 0;
delegation.__DEPRECATED_tokensLockedUntil = 0;
emit StakeDelegatedWithdrawn(serviceProvider, delegator, tokensToWithdraw);
// -- Interactions --
// Return tokens to the delegator
_graphToken().pushTokens(delegator, tokensToWithdraw);
return tokensToWithdraw;
}
/*
* SLASHING
*/
/// @inheritdoc IHorizonStakingMain
function slash(
address serviceProvider,
uint256 tokens,
uint256 tokensVerifier,
address verifierDestination
) external override notPaused {
// TRANSITION PERIOD: remove after the transition period
// Check if sender is authorized to slash on the deprecated list
if (__DEPRECATED_slashers[msg.sender]) {
// Forward call to staking extension
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = STAKING_EXTENSION_ADDRESS.delegatecall(
abi.encodeCall(
IHorizonStakingExtension.legacySlash,
(serviceProvider, tokens, tokensVerifier, verifierDestination)
)
);
require(success, HorizonStakingLegacySlashFailed());
return;
}
address verifier = msg.sender;
Provision storage prov = _provisions[serviceProvider][verifier];
DelegationPoolInternal storage pool = _getDelegationPool(serviceProvider, verifier);
uint256 tokensProvisionTotal = prov.tokens + pool.tokens;
require(tokensProvisionTotal != 0, HorizonStakingNoTokensToSlash());
uint256 tokensToSlash = MathUtils.min(tokens, tokensProvisionTotal);
// Slash service provider first
// - A portion goes to verifier as reward
// - A portion gets burned
uint256 providerTokensSlashed = MathUtils.min(prov.tokens, tokensToSlash);
if (providerTokensSlashed > 0) {
// Pay verifier reward - must be within the maxVerifierCut percentage
uint256 maxVerifierTokens = providerTokensSlashed.mulPPM(prov.maxVerifierCut);
require(
maxVerifierTokens >= tokensVerifier,
HorizonStakingTooManyTokens(tokensVerifier, maxVerifierTokens)
);
if (tokensVerifier > 0) {
_graphToken().pushTokens(verifierDestination, tokensVerifier);
emit VerifierTokensSent(serviceProvider, verifier, verifierDestination, tokensVerifier);
}
// Burn remainder
_graphToken().burnTokens(providerTokensSlashed - tokensVerifier);
// Provision accounting - round down, 1 wei max precision loss
prov.tokensThawing = (prov.tokensThawing * (prov.tokens - providerTokensSlashed)) / prov.tokens;
prov.tokens = prov.tokens - providerTokensSlashed;
// If the slashing leaves the thawing shares with no thawing tokens, cancel pending thawings by:
// - deleting all thawing shares
// - incrementing the nonce to invalidate pending thaw requests
if (prov.sharesThawing != 0 && prov.tokensThawing == 0) {
prov.sharesThawing = 0;
prov.thawingNonce++;
}
// Service provider accounting
_serviceProviders[serviceProvider].tokensProvisioned =
_serviceProviders[serviceProvider].tokensProvisioned - providerTokensSlashed;
_serviceProviders[serviceProvider].tokensStaked =
_serviceProviders[serviceProvider].tokensStaked - providerTokensSlashed;
emit ProvisionSlashed(serviceProvider, verifier, providerTokensSlashed);
}
// Slash delegators if needed
// - Slashed delegation is entirely burned
// Since tokensToSlash is already limited above, this subtraction will remain within pool.tokens.
tokensToSlash = tokensToSlash - providerTokensSlashed;
if (tokensToSlash > 0) {
if (_delegationSlashingEnabled) {
// Burn tokens
_graphToken().burnTokens(tokensToSlash);
// Delegation pool accounting - round down, 1 wei max precision loss
pool.tokensThawing = (pool.tokensThawing * (pool.tokens - tokensToSlash)) / pool.tokens;
pool.tokens = pool.tokens - tokensToSlash;
// If the slashing leaves the thawing shares with no thawing tokens, cancel pending thawings by:
// - deleting all thawing shares
// - incrementing the nonce to invalidate pending thaw requests
// Note that thawing shares are completely lost, delegators won't get back the corresponding
// delegation pool shares.
if (pool.sharesThawing != 0 && pool.tokensThawing == 0) {
pool.sharesThawing = 0;
pool.thawingNonce++;
}
emit DelegationSlashed(serviceProvider, verifier, tokensToSlash);
} else {
emit DelegationSlashingSkipped(serviceProvider, verifier, tokensToSlash);
}
}
}
/*
* LOCKED VERIFIERS
*/
/// @inheritdoc IHorizonStakingMain
function provisionLocked(
address serviceProvider,
address verifier,
uint256 tokens,
uint32 maxVerifierCut,
uint64 thawingPeriod
) external override notPaused onlyAuthorized(serviceProvider, verifier) {
require(_allowedLockedVerifiers[verifier], HorizonStakingVerifierNotAllowed(verifier));
_createProvision(serviceProvider, tokens, verifier, maxVerifierCut, thawingPeriod);
}
/// @inheritdoc IHorizonStakingMain
function setOperatorLocked(address verifier, address operator, bool allowed) external override notPaused {
require(_allowedLockedVerifiers[verifier], HorizonStakingVerifierNotAllowed(verifier));
_setOperator(verifier, operator, allowed);
}
/*
* GOVERNANCE
*/
/// @inheritdoc IHorizonStakingMain
function setAllowedLockedVerifier(address verifier, bool allowed) external override onlyGovernor {
_allowedLockedVerifiers[verifier] = allowed;
emit AllowedLockedVerifierSet(verifier, allowed);
}
/// @inheritdoc IHorizonStakingMain
function setDelegationSlashingEnabled() external override onlyGovernor {
_delegationSlashingEnabled = true;
emit DelegationSlashingEnabled();
}
/// @inheritdoc IHorizonStakingMain
function clearThawingPeriod() external override onlyGovernor {
__DEPRECATED_thawingPeriod = 0;
emit ThawingPeriodCleared();
}
/// @inheritdoc IHorizonStakingMain
function setMaxThawingPeriod(uint64 maxThawingPeriod) external override onlyGovernor {
_maxThawingPeriod = maxThawingPeriod;
emit MaxThawingPeriodSet(_maxThawingPeriod);
}
/*
* OPERATOR
*/
/// @inheritdoc IHorizonStakingMain
function setOperator(address verifier, address operator, bool allowed) external override notPaused {
_setOperator(verifier, operator, allowed);
}
/// @inheritdoc IHorizonStakingMain
function isAuthorized(
address serviceProvider,
address verifier,
address operator
) external view override returns (bool) {
return _isAuthorized(serviceProvider, verifier, operator);
}
/*
* GETTERS
*/
/// @inheritdoc IHorizonStakingMain
function getStakingExtension() external view override returns (address) {
return STAKING_EXTENSION_ADDRESS;
}
/*
* PRIVATE FUNCTIONS
*/
/**
* @notice Deposit tokens on the service provider stake, on behalf of the service provider.
* @dev Pulls tokens from the caller.
* @param _serviceProvider Address of the service provider
* @param _tokens Amount of tokens to stake
*/
function _stakeTo(address _serviceProvider, uint256 _tokens) private {
require(_tokens != 0, HorizonStakingInvalidZeroTokens());
// Transfer tokens to stake from caller to this contract
_graphToken().pullTokens(msg.sender, _tokens);
// Stake the transferred tokens
_stake(_serviceProvider, _tokens);
}
/**
* @notice Move idle stake back to the owner's account.
* Stake is removed from the protocol:
* - During the transition period it's locked for a period of time before it can be withdrawn
* by calling {withdraw}.
* - After the transition period it's immediately withdrawn.
* Note that after the transition period if there are tokens still locked they will have to be
* withdrawn by calling {withdraw}.
* @param _tokens Amount of tokens to unstake
*/
function _unstake(uint256 _tokens) private {
address serviceProvider = msg.sender;
require(_tokens != 0, HorizonStakingInvalidZeroTokens());
uint256 tokensIdle = _getIdleStake(serviceProvider);
require(_tokens <= tokensIdle, HorizonStakingInsufficientIdleStake(_tokens, tokensIdle));
ServiceProviderInternal storage sp = _serviceProviders[serviceProvider];
uint256 stakedTokens = sp.tokensStaked;
// This is also only during the transition period: we need
// to ensure tokens stay locked after closing legacy allocations.
// After sufficient time (56 days?) we should remove the closeAllocation function
// and set the thawing period to 0.
uint256 lockingPeriod = __DEPRECATED_thawingPeriod;
if (lockingPeriod == 0) {
sp.tokensStaked = stakedTokens - _tokens;
_graphToken().pushTokens(serviceProvider, _tokens);
emit HorizonStakeWithdrawn(serviceProvider, _tokens);
} else {
// Before locking more tokens, withdraw any unlocked ones if possible
if (sp.__DEPRECATED_tokensLocked != 0 && block.number >= sp.__DEPRECATED_tokensLockedUntil) {
_withdraw(serviceProvider);
}
// TRANSITION PERIOD: remove after the transition period
// Take into account period averaging for multiple unstake requests
if (sp.__DEPRECATED_tokensLocked > 0) {
lockingPeriod = MathUtils.weightedAverageRoundingUp(
MathUtils.diffOrZero(sp.__DEPRECATED_tokensLockedUntil, block.number), // Remaining thawing period
sp.__DEPRECATED_tokensLocked, // Weighted by remaining unstaked tokens
lockingPeriod, // Thawing period
_tokens // Weighted by new tokens to unstake
);
}
// Update balances
sp.__DEPRECATED_tokensLocked = sp.__DEPRECATED_tokensLocked + _tokens;
sp.__DEPRECATED_tokensLockedUntil = block.number + lockingPeriod;
emit HorizonStakeLocked(serviceProvider, sp.__DEPRECATED_tokensLocked, sp.__DEPRECATED_tokensLockedUntil);
}
}
/**
* @notice Withdraw service provider tokens once the thawing period (initiated by {unstake}) has passed.
* All thawed tokens are withdrawn.
* @dev TRANSITION PERIOD: This is only needed during the transition period while we still have
* a global lock. After that, unstake() will automatically withdraw.
* @param _serviceProvider Address of service provider to withdraw funds from
*/
function _withdraw(address _serviceProvider) private {
// Get tokens available for withdraw and update balance
ServiceProviderInternal storage sp = _serviceProviders[_serviceProvider];
uint256 tokensToWithdraw = sp.__DEPRECATED_tokensLocked;
require(tokensToWithdraw != 0, HorizonStakingInvalidZeroTokens());
require(
block.number >= sp.__DEPRECATED_tokensLockedUntil,
HorizonStakingStillThawing(sp.__DEPRECATED_tokensLockedUntil)
);
// Reset locked tokens
sp.__DEPRECATED_tokensLocked = 0;
sp.__DEPRECATED_tokensLockedUntil = 0;
sp.tokensStaked = sp.tokensStaked - tokensToWithdraw;
// Return tokens to the service provider
_graphToken().pushTokens(_serviceProvider, tokensToWithdraw);
emit HorizonStakeWithdrawn(_serviceProvider, tokensToWithdraw);
}
/**
* @notice Provision stake to a verifier. The tokens will be locked with a thawing period
* and will be slashable by the verifier. This is the main mechanism to provision stake to a data
* service, where the data service is the verifier.
* This function can be called by the service provider or by an operator authorized by the provider
* for this specific verifier.
* @dev TRANSITION PERIOD: During the transition period, only the subgraph data service can be used as a verifier. This
* prevents an escape hatch for legacy allocation stake.
* @param _serviceProvider The service provider address
* @param _verifier The verifier address for which the tokens are provisioned (who will be able to slash the tokens)
* @param _tokens The amount of tokens that will be locked and slashable
* @param _maxVerifierCut The maximum cut, expressed in PPM, that a verifier can transfer instead of burning when slashing
* @param _thawingPeriod The period in seconds that the tokens will be thawing before they can be removed from the provision
*/
function _createProvision(
address _serviceProvider,
uint256 _tokens,
address _verifier,
uint32 _maxVerifierCut,
uint64 _thawingPeriod
) private {
require(_tokens > 0, HorizonStakingInvalidZeroTokens());
// TRANSITION PERIOD: Remove this after the transition period - it prevents an early escape hatch for legacy allocations
require(
_verifier == SUBGRAPH_DATA_SERVICE_ADDRESS || __DEPRECATED_thawingPeriod == 0,
HorizonStakingInvalidVerifier(_verifier)
);
require(PPMMath.isValidPPM(_maxVerifierCut), HorizonStakingInvalidMaxVerifierCut(_maxVerifierCut));
require(
_thawingPeriod <= _maxThawingPeriod,
HorizonStakingInvalidThawingPeriod(_thawingPeriod, _maxThawingPeriod)
);
require(_provisions[_serviceProvider][_verifier].createdAt == 0, HorizonStakingProvisionAlreadyExists());
uint256 tokensIdle = _getIdleStake(_serviceProvider);
require(_tokens <= tokensIdle, HorizonStakingInsufficientIdleStake(_tokens, tokensIdle));
_provisions[_serviceProvider][_verifier] = Provision({
tokens: _tokens,
tokensThawing: 0,
sharesThawing: 0,
maxVerifierCut: _maxVerifierCut,
thawingPeriod: _thawingPeriod,
createdAt: uint64(block.timestamp),
maxVerifierCutPending: _maxVerifierCut,
thawingPeriodPending: _thawingPeriod,
lastParametersStagedAt: 0,
thawingNonce: 0
});
ServiceProviderInternal storage sp = _serviceProviders[_serviceProvider];
sp.tokensProvisioned = sp.tokensProvisioned + _tokens;
emit ProvisionCreated(_serviceProvider, _verifier, _tokens, _maxVerifierCut, _thawingPeriod);
}
/**
* @notice Adds tokens from the service provider's idle stake to a provision
* @param _serviceProvider The service provider address
* @param _verifier The verifier address
* @param _tokens The amount of tokens to add to the provision
*/
function _addToProvision(address _serviceProvider, address _verifier, uint256 _tokens) private {
require(_tokens != 0, HorizonStakingInvalidZeroTokens());
Provision storage prov = _provisions[_serviceProvider][_verifier];
require(prov.createdAt != 0, HorizonStakingInvalidProvision(_serviceProvider, _verifier));
uint256 tokensIdle = _getIdleStake(_serviceProvider);
require(_tokens <= tokensIdle, HorizonStakingInsufficientIdleStake(_tokens, tokensIdle));
prov.tokens = prov.tokens + _tokens;
_serviceProviders[_serviceProvider].tokensProvisioned =
_serviceProviders[_serviceProvider].tokensProvisioned + _tokens;
emit ProvisionIncreased(_serviceProvider, _verifier, _tokens);
}
/**
* @notice Start thawing tokens to remove them from a provision.
* This function can be called by the service provider or by an operator authorized by the provider
* for this specific verifier.
*
* Note that removing tokens from a provision is a two step process:
* - First the tokens are thawed using this function.
* - Then after the thawing period, the tokens are removed from the provision using {deprovision}
* or {reprovision}.
*
* @dev We use a thawing pool to keep track of tokens thawing for multiple thaw requests.
* If due to slashing the thawing pool loses all of its tokens, the pool is reset and all pending thaw
* requests are invalidated.
*
* @param _serviceProvider The service provider address
* @param _verifier The verifier address for which the tokens are provisioned
* @param _tokens The amount of tokens to thaw
* @return The ID of the thaw request
*/
function _thaw(address _serviceProvider, address _verifier, uint256 _tokens) private returns (bytes32) {
require(_tokens != 0, HorizonStakingInvalidZeroTokens());
uint256 tokensAvailable = _getProviderTokensAvailable(_serviceProvider, _verifier);
require(tokensAvailable >= _tokens, HorizonStakingInsufficientTokens(tokensAvailable, _tokens));
Provision storage prov = _provisions[_serviceProvider][_verifier];
// Calculate shares to issue
// Thawing pool is reset/initialized when the pool is empty: prov.tokensThawing == 0
// Round thawing shares up to ensure fairness and avoid undervaluing the shares due to rounding down.
uint256 thawingShares = prov.tokensThawing == 0
? _tokens
: ((prov.sharesThawing * _tokens + prov.tokensThawing - 1) / prov.tokensThawing);
uint64 thawingUntil = uint64(block.timestamp + uint256(prov.thawingPeriod));
prov.sharesThawing = prov.sharesThawing + thawingShares;
prov.tokensThawing = prov.tokensThawing + _tokens;
bytes32 thawRequestId = _createThawRequest(
ThawRequestType.Provision,
_serviceProvider,
_verifier,
_serviceProvider,
thawingShares,
thawingUntil,
prov.thawingNonce
);
emit ProvisionThawed(_serviceProvider, _verifier, _tokens);
return thawRequestId;
}
/**
* @notice Remove tokens from a provision and move them back to the service provider's idle stake.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error. Otherwise, the function
* will attempt to fulfill all thaw requests until the first one that is not yet expired is found.
* @param _serviceProvider The service provider address
* @param _verifier The verifier address
* @param _nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
* @return The amount of tokens that were removed from the provision
*/
function _deprovision(
address _serviceProvider,
address _verifier,
uint256 _nThawRequests
) private returns (uint256) {
Provision storage prov = _provisions[_serviceProvider][_verifier];
uint256 tokensThawed_ = 0;
uint256 sharesThawing = prov.sharesThawing;
uint256 tokensThawing = prov.tokensThawing;
FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
requestType: ThawRequestType.Provision,
serviceProvider: _serviceProvider,
verifier: _verifier,
owner: _serviceProvider,
tokensThawing: tokensThawing,
sharesThawing: sharesThawing,
nThawRequests: _nThawRequests,
thawingNonce: prov.thawingNonce
});
(tokensThawed_, tokensThawing, sharesThawing) = _fulfillThawRequests(params);
prov.tokens = prov.tokens - tokensThawed_;
prov.sharesThawing = sharesThawing;
prov.tokensThawing = tokensThawing;
_serviceProviders[_serviceProvider].tokensProvisioned -= tokensThawed_;
emit TokensDeprovisioned(_serviceProvider, _verifier, tokensThawed_);
return tokensThawed_;
}
/**
* @notice Delegate tokens to a provision.
* @dev Note that this function does not pull the delegated tokens from the caller. It expects that to
* have been done before calling this function.
* @param _serviceProvider The service provider address
* @param _verifier The verifier address
* @param _tokens The amount of tokens to delegate
* @param _minSharesOut The minimum amount of shares to accept, slippage protection.
*/
function _delegate(address _serviceProvider, address _verifier, uint256 _tokens, uint256 _minSharesOut) private {
// Enforces a minimum delegation amount to prevent share manipulation attacks.
// This stops attackers from inflating share value and blocking other delegators.
require(_tokens >= MIN_DELEGATION, HorizonStakingInsufficientDelegationTokens(_tokens, MIN_DELEGATION));
require(
_provisions[_serviceProvider][_verifier].createdAt != 0,
HorizonStakingInvalidProvision(_serviceProvider, _verifier)
);
DelegationPoolInternal storage pool = _getDelegationPool(_serviceProvider, _verifier);
DelegationInternal storage delegation = pool.delegators[msg.sender];
// An invalid delegation pool has shares but no tokens
require(
pool.tokens != 0 || pool.shares == 0,
HorizonStakingInvalidDelegationPoolState(_serviceProvider, _verifier)
);
// Calculate shares to issue
// Delegation pool is reset/initialized in any of the following cases:
// - pool.tokens == 0 and pool.shares == 0, pool is completely empty. Note that we don't test shares == 0 because
// the invalid delegation pool check already ensures shares are 0 if tokens are 0
// - pool.tokens == pool.tokensThawing, the entire pool is thawing
bool initializePool = pool.tokens == 0 || pool.tokens == pool.tokensThawing;
uint256 shares = initializePool ? _tokens : ((_tokens * pool.shares) / (pool.tokens - pool.tokensThawing));
require(shares != 0 && shares >= _minSharesOut, HorizonStakingSlippageProtection(shares, _minSharesOut));
pool.tokens = pool.tokens + _tokens;
pool.shares = pool.shares + shares;
delegation.shares = delegation.shares + shares;
emit TokensDelegated(_serviceProvider, _verifier, msg.sender, _tokens, shares);
}
/**
* @notice Undelegate tokens from a provision and start thawing them.
* Note that undelegating tokens from a provision is a two step process:
* - First the tokens are thawed using this function.
* - Then after the thawing period, the tokens are removed from the provision using {withdrawDelegated}.
* @dev To allow delegation to be slashable even while thawing without breaking accounting
* the delegation pool shares are burned and replaced with thawing pool shares.
* @dev Note that due to slashing the delegation pool can enter an invalid state if all it's tokens are slashed.
* An invalid pool can only be recovered by adding back tokens into the pool with {IHorizonStakingMain-addToDelegationPool}.
* Any time the delegation pool is invalidated, the thawing pool is also reset and any pending undelegate requests get
* invalidated.
* @dev Note that delegation that is caught thawing when the pool is invalidated will be completely lost! However delegation shares
* that were not thawing will be preserved.
* @param _serviceProvider The service provider address
* @param _verifier The verifier address
* @param _shares The amount of shares to undelegate
* @return The ID of the thaw request
*/
function _undelegate(address _serviceProvider, address _verifier, uint256 _shares) private returns (bytes32) {
require(_shares > 0, HorizonStakingInvalidZeroShares());
DelegationPoolInternal storage pool = _getDelegationPool(_serviceProvider, _verifier);
DelegationInternal storage delegation = pool.delegators[msg.sender];
require(delegation.shares >= _shares, HorizonStakingInsufficientShares(delegation.shares, _shares));
// An invalid delegation pool has shares but no tokens (previous require check ensures shares > 0)
require(pool.tokens != 0, HorizonStakingInvalidDelegationPoolState(_serviceProvider, _verifier));
// Calculate thawing shares to issue - convert delegation pool shares to thawing pool shares
// delegation pool shares -> delegation pool tokens -> thawing pool shares
// Thawing pool is reset/initialized when the pool is empty: prov.tokensThawing == 0
uint256 tokens = (_shares * (pool.tokens - pool.tokensThawing)) / pool.shares;
// Thawing shares are rounded down to protect the pool and avoid taking extra tokens from other participants.
uint256 thawingShares = pool.tokensThawing == 0 ? tokens : ((tokens * pool.sharesThawing) / pool.tokensThawing);
uint64 thawingUntil = uint64(block.timestamp + uint256(_provisions[_serviceProvider][_verifier].thawingPeriod));
pool.tokensThawing = pool.tokensThawing + tokens;
pool.sharesThawing = pool.sharesThawing + thawingShares;
pool.shares = pool.shares - _shares;
delegation.shares = delegation.shares - _shares;
if (delegation.shares != 0) {
uint256 remainingTokens = (delegation.shares * (pool.tokens - pool.tokensThawing)) / pool.shares;
require(
remainingTokens >= MIN_DELEGATION,
HorizonStakingInsufficientTokens(remainingTokens, MIN_DELEGATION)
);
}
bytes32 thawRequestId = _createThawRequest(
ThawRequestType.Delegation,
_serviceProvider,
_verifier,
msg.sender,
thawingShares,
thawingUntil,
pool.thawingNonce
);
emit TokensUndelegated(_serviceProvider, _verifier, msg.sender, tokens, _shares);
return thawRequestId;
}
/**
* @notice Withdraw undelegated tokens from a provision after thawing.
* @dev The parameter `nThawRequests` can be set to a non zero value to fulfill a specific number of thaw
* requests in the event that fulfilling all of them results in a gas limit error. Otherwise, the function
* will attempt to fulfill all thaw requests until the first one that is not yet expired is found.
* @dev If the delegation pool was completely slashed before withdrawing, calling this function will fulfill
* the thaw requests with an amount equal to zero.
* @param _serviceProvider The service provider address
* @param _verifier The verifier address
* @param _newServiceProvider The new service provider address
* @param _newVerifier The new verifier address
* @param _minSharesForNewProvider The minimum number of shares for the new service provider
* @param _nThawRequests The number of thaw requests to fulfill. Set to 0 to fulfill all thaw requests.
*/
function _withdrawDelegated(
address _serviceProvider,
address _verifier,
address _newServiceProvider,
address _newVerifier,
uint256 _minSharesForNewProvider,
uint256 _nThawRequests
) private {
DelegationPoolInternal storage pool = _getDelegationPool(_serviceProvider, _verifier);
// An invalid delegation pool has shares but no tokens
require(
pool.tokens != 0 || pool.shares == 0,
HorizonStakingInvalidDelegationPoolState(_serviceProvider, _verifier)
);
uint256 tokensThawed = 0;
uint256 sharesThawing = pool.sharesThawing;
uint256 tokensThawing = pool.tokensThawing;
FulfillThawRequestsParams memory params = FulfillThawRequestsParams({
requestType: ThawRequestType.Delegation,
serviceProvider: _serviceProvider,
verifier: _verifier,
owner: msg.sender,
tokensThawing: tokensThawing,
sharesThawing: sharesThawing,
nThawRequests: _nThawRequests,
thawingNonce: pool.thawingNonce
});
(tokensThawed, tokensThawing, sharesThawing) = _fulfillThawRequests(params);
// The next subtraction should never revert becase: pool.tokens >= pool.tokensThawing and pool.tokensThawing >= tokensThawed
// In the event the pool gets completely slashed tokensThawed will fulfil to 0.
pool.tokens = pool.tokens - tokensThawed;
pool.sharesThawing = sharesThawing;
pool.tokensThawing = tokensThawing;
if (tokensThawed != 0) {
if (_newServiceProvider != address(0) && _newVerifier != address(0)) {
_delegate(_newServiceProvider, _newVerifier, tokensThawed, _minSharesForNewProvider);
} else {
_graphToken().pushTokens(msg.sender, tokensThawed);
emit DelegatedTokensWithdrawn(_serviceProvider, _verifier, msg.sender, tokensThawed);
}
}
}
/**
* @notice Creates a thaw request.
* Allows creating thaw requests up to a maximum of `MAX_THAW_REQUESTS` per owner.
* Thaw requests are stored in a linked list per owner (and service provider, verifier) to allow for efficient
* processing.
* @param _requestType The type of thaw request.
* @param _serviceProvider The address of the service provider
* @param _verifier The address of the verifier
* @param _owner The address of the owner of the thaw request
* @param _shares The number of shares to thaw
* @param _thawingUntil The timestamp until which the shares are thawing
* @param _thawingNonce Owner's validity nonce for the thaw request
* @return The ID of the thaw request
*/
function _createThawRequest(
ThawRequestType _requestType,
address _serviceProvider,
address _verifier,
address _owner,
uint256 _shares,
uint64 _thawingUntil,
uint256 _thawingNonce
) private returns (bytes32) {
require(_shares != 0, HorizonStakingInvalidZeroShares());
ILinkedList.List storage thawRequestList = _getThawRequestList(
_requestType,
_serviceProvider,
_verifier,
_owner
);
require(thawRequestList.count < MAX_THAW_REQUESTS, HorizonStakingTooManyThawRequests());
bytes32 thawRequestId = keccak256(abi.encodePacked(_serviceProvider, _verifier, _owner, thawRequestList.nonce));
ThawRequest storage thawRequest = _getThawRequest(_requestType, thawRequestId);
thawRequest.shares = _shares;
thawRequest.thawingUntil = _thawingUntil;
thawRequest.nextRequest = bytes32(0);
thawRequest.thawingNonce = _thawingNonce;
if (thawRequestList.count != 0) _getThawRequest(_requestType, thawRequestList.tail).nextRequest = thawRequestId;
thawRequestList.addTail(thawRequestId);
emit ThawRequestCreated(
_requestType,
_serviceProvider,
_verifier,
_owner,
_shares,
_thawingUntil,
thawRequestId,
_thawingNonce
);
return thawRequestId;
}
/**
* @notice Traverses a thaw request list and fulfills expired thaw requests.
* @dev Note that the list is traversed by creation date not by thawing until date. Traversing will stop
* when the first thaw request that is not yet expired is found even if later thaw requests have expired. This
* could happen for example when the thawing period is shortened.
* @param _params The parameters for fulfilling thaw requests
* @return The amount of thawed tokens
* @return The amount of tokens still thawing
* @return The amount of shares still thawing
*/
function _fulfillThawRequests(
FulfillThawRequestsParams memory _params
) private returns (uint256, uint256, uint256) {
ILinkedList.List storage thawRequestList = _getThawRequestList(
_params.requestType,
_params.serviceProvider,
_params.verifier,
_params.owner
);
require(thawRequestList.count > 0, HorizonStakingNothingThawing());
TraverseThawRequestsResults memory results = _traverseThawRequests(_params, thawRequestList);
emit ThawRequestsFulfilled(
_params.requestType,
_params.serviceProvider,
_params.verifier,
_params.owner,
results.requestsFulfilled,
results.tokensThawed
);
return (results.tokensThawed, results.tokensThawing, results.sharesThawing);
}
/**
* @notice Traverses a thaw request list and fulfills expired thaw requests.
* @param _params The parameters for fulfilling thaw requests
* @param _thawRequestList The list of thaw requests to traverse
* @return The results of the traversal
*/
function _traverseThawRequests(
FulfillThawRequestsParams memory _params,
ILinkedList.List storage _thawRequestList
) private returns (TraverseThawRequestsResults memory) {
function(bytes32) view returns (bytes32) getNextItem = _getNextThawRequest(_params.requestType);
function(bytes32) deleteItem = _getDeleteThawRequest(_params.requestType);
bytes memory acc = abi.encode(
_params.requestType,
uint256(0),
_params.tokensThawing,
_params.sharesThawing,
_params.thawingNonce
);
(uint256 thawRequestsFulfilled, bytes memory data) = _thawRequestList.traverse(
getNextItem,
_fulfillThawRequest,
deleteItem,
acc,
_params.nThawRequests
);
(, uint256 tokensThawed, uint256 tokensThawing, uint256 sharesThawing) = abi.decode(
data,
(ThawRequestType, uint256, uint256, uint256)
);
return
TraverseThawRequestsResults({
requestsFulfilled: thawRequestsFulfilled,
tokensThawed: tokensThawed,
tokensThawing: tokensThawing,
sharesThawing: sharesThawing
});
}
/**
* @notice Fulfills a thaw request.
* @dev This function is used as a callback in the thaw requests linked list traversal.
* @param _thawRequestId The ID of the current thaw request
* @param _acc The accumulator data for the thaw requests being fulfilled
* @return Whether the thaw request is still thawing, indicating that the traversal should continue or stop.
* @return The updated accumulator data
*/
function _fulfillThawRequest(bytes32 _thawRequestId, bytes memory _acc) private returns (bool, bytes memory) {
// decode
(
ThawRequestType requestType,
uint256 tokensThawed,
uint256 tokensThawing,
uint256 sharesThawing,
uint256 thawingNonce
) = abi.decode(_acc, (ThawRequestType, uint256, uint256, uint256, uint256));
ThawRequest storage thawRequest = _getThawRequest(requestType, _thawRequestId);
// early exit
if (thawRequest.thawingUntil > block.timestamp) {
return (true, LinkedList.NULL_BYTES);
}
// process - only fulfill thaw requests for the current valid nonce
uint256 tokens = 0;
bool validThawRequest = thawRequest.thawingNonce == thawingNonce;
if (validThawRequest) {
// sharesThawing cannot be zero if there is a valid thaw request so the next division is safe
tokens = (thawRequest.shares * tokensThawing) / sharesThawing;
tokensThawing = tokensThawing - tokens;
sharesThawing = sharesThawing - thawRequest.shares;
tokensThawed = tokensThawed + tokens;
}
emit ThawRequestFulfilled(
requestType,
_thawRequestId,
tokens,
thawRequest.shares,
thawRequest.thawingUntil,
validThawRequest
);
// encode
_acc = abi.encode(requestType, tokensThawed, tokensThawing, sharesThawing, thawingNonce);
return (false, _acc);
}
/**
* @notice Deletes a thaw request for a provision.
* @param _thawRequestId The ID of the thaw request to delete.
*/
function _deleteProvisionThawRequest(bytes32 _thawRequestId) private {
delete _thawRequests[ThawRequestType.Provision][_thawRequestId];
}
/**
* @notice Deletes a thaw request for a delegation.
* @param _thawRequestId The ID of the thaw request to delete.
*/
function _deleteDelegationThawRequest(bytes32 _thawRequestId) private {
delete _thawRequests[ThawRequestType.Delegation][_thawRequestId];
}
/**
* @notice Authorize or unauthorize an address to be an operator for the caller on a data service.
* @dev Note that this function handles the special case where the verifier is the subgraph data service,
* where the operator settings are stored in the legacy mapping.
* @param _verifier The verifier / data service on which they'll be allowed to operate
* @param _operator Address to authorize or unauthorize
* @param _allowed Whether the operator is authorized or not
*/
function _setOperator(address _verifier, address _operator, bool _allowed) private {
require(_operator != msg.sender, HorizonStakingCallerIsServiceProvider());
if (_verifier == SUBGRAPH_DATA_SERVICE_ADDRESS) {
_legacyOperatorAuth[msg.sender][_operator] = _allowed;
} else {
_operatorAuth[msg.sender][_verifier][_operator] = _allowed;
}
emit OperatorSet(msg.sender, _verifier, _operator, _allowed);
}
/**
* @notice Check if an operator is authorized for the caller on a specific verifier / data service.
* @dev Note that this function handles the special case where the verifier is the subgraph data service,
* where the operator settings are stored in the legacy mapping.
* @param _serviceProvider The service provider on behalf of whom they're claiming to act
* @param _verifier The verifier / data service on which they're claiming to act
* @param _operator The address to check for auth
* @return Whether the operator is authorized or not
*/
function _isAuthorized(address _serviceProvider, address _verifier, address _operator) private view returns (bool) {
if (_operator == _serviceProvider) {
return true;
}
if (_verifier == SUBGRAPH_DATA_SERVICE_ADDRESS) {
return _legacyOperatorAuth[_serviceProvider][_operator];
} else {
return _operatorAuth[_serviceProvider][_verifier][_operator];
}
}
/**
* @notice Determines the correct callback function for `deleteItem` based on the request type.
* @param _requestType The type of thaw request (Provision or Delegation).
* @return A function pointer to the appropriate `deleteItem` callback.
*/
function _getDeleteThawRequest(ThawRequestType _requestType) private pure returns (function(bytes32)) {
if (_requestType == ThawRequestType.Provision) {
return _deleteProvisionThawRequest;
} else if (_requestType == ThawRequestType.Delegation) {
return _deleteDelegationThawRequest;
} else {
revert HorizonStakingInvalidThawRequestType();
}
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol";
import { IHorizonStakingBase } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingBase.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
import { MathUtils } from "../libraries/MathUtils.sol";
import { LinkedList } from "../libraries/LinkedList.sol";
import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol";
import { GraphUpgradeable } from "@graphprotocol/contracts/contracts/upgrades/GraphUpgradeable.sol";
import { Managed } from "./utilities/Managed.sol";
import { HorizonStakingV1Storage } from "./HorizonStakingStorage.sol";
/**
* @title HorizonStakingBase contract
* @notice This contract is the base staking contract implementing storage getters for both internal
* and external use.
* @dev Implementation of the {IHorizonStakingBase} interface.
* @dev It's meant to be inherited by the {HorizonStaking} and {HorizonStakingExtension}
* contracts so some internal functions are also included here.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract HorizonStakingBase is
Multicall,
Managed,
HorizonStakingV1Storage,
GraphUpgradeable,
IHorizonStakingTypes,
IHorizonStakingBase
{
using LinkedList for ILinkedList.List;
/**
* @notice The address of the subgraph data service.
* @dev Require to handle the special case when the verifier is the subgraph data service.
*/
address internal immutable SUBGRAPH_DATA_SERVICE_ADDRESS;
/**
* @dev The staking contract is upgradeable however we still use the constructor to set
* a few immutable variables.
* @param controller The address of the Graph controller contract.
* @param subgraphDataServiceAddress The address of the subgraph data service.
*/
constructor(address controller, address subgraphDataServiceAddress) Managed(controller) {
SUBGRAPH_DATA_SERVICE_ADDRESS = subgraphDataServiceAddress;
}
/// @inheritdoc IHorizonStakingBase
/// @dev Removes deprecated fields from the return value.
function getServiceProvider(address serviceProvider) external view override returns (ServiceProvider memory) {
ServiceProvider memory sp;
ServiceProviderInternal storage spInternal = _serviceProviders[serviceProvider];
sp.tokensStaked = spInternal.tokensStaked;
sp.tokensProvisioned = spInternal.tokensProvisioned;
return sp;
}
/// @inheritdoc IHorizonStakingBase
function getStake(address serviceProvider) external view override returns (uint256) {
return _serviceProviders[serviceProvider].tokensStaked;
}
/// @inheritdoc IHorizonStakingBase
function getIdleStake(address serviceProvider) external view override returns (uint256) {
return _getIdleStake(serviceProvider);
}
/// @inheritdoc IHorizonStakingBase
/// @dev Removes deprecated fields from the return value.
function getDelegationPool(
address serviceProvider,
address verifier
) external view override returns (DelegationPool memory) {
DelegationPool memory pool;
DelegationPoolInternal storage poolInternal = _getDelegationPool(serviceProvider, verifier);
pool.tokens = poolInternal.tokens;
pool.shares = poolInternal.shares;
pool.tokensThawing = poolInternal.tokensThawing;
pool.sharesThawing = poolInternal.sharesThawing;
pool.thawingNonce = poolInternal.thawingNonce;
return pool;
}
/// @inheritdoc IHorizonStakingBase
/// @dev Removes deprecated fields from the return value.
function getDelegation(
address serviceProvider,
address verifier,
address delegator
) external view override returns (Delegation memory) {
Delegation memory delegation;
DelegationPoolInternal storage poolInternal = _getDelegationPool(serviceProvider, verifier);
delegation.shares = poolInternal.delegators[delegator].shares;
return delegation;
}
/// @inheritdoc IHorizonStakingBase
function getDelegationFeeCut(
address serviceProvider,
address verifier,
IGraphPayments.PaymentTypes paymentType
) external view override returns (uint256) {
return _delegationFeeCut[serviceProvider][verifier][paymentType];
}
/// @inheritdoc IHorizonStakingBase
function getProvision(address serviceProvider, address verifier) external view override returns (Provision memory) {
return _provisions[serviceProvider][verifier];
}
/// @inheritdoc IHorizonStakingBase
function getTokensAvailable(
address serviceProvider,
address verifier,
uint32 delegationRatio
) external view override returns (uint256) {
uint256 tokensAvailableProvider = _getProviderTokensAvailable(serviceProvider, verifier);
uint256 tokensAvailableDelegated = _getDelegatedTokensAvailable(serviceProvider, verifier);
uint256 tokensDelegatedMax = tokensAvailableProvider * (uint256(delegationRatio));
uint256 tokensDelegatedCapacity = MathUtils.min(tokensAvailableDelegated, tokensDelegatedMax);
return tokensAvailableProvider + tokensDelegatedCapacity;
}
/// @inheritdoc IHorizonStakingBase
function getProviderTokensAvailable(
address serviceProvider,
address verifier
) external view override returns (uint256) {
return _getProviderTokensAvailable(serviceProvider, verifier);
}
/// @inheritdoc IHorizonStakingBase
function getDelegatedTokensAvailable(
address serviceProvider,
address verifier
) external view override returns (uint256) {
return _getDelegatedTokensAvailable(serviceProvider, verifier);
}
/// @inheritdoc IHorizonStakingBase
function getThawRequest(
ThawRequestType requestType,
bytes32 thawRequestId
) external view override returns (ThawRequest memory) {
return _getThawRequest(requestType, thawRequestId);
}
/// @inheritdoc IHorizonStakingBase
function getThawRequestList(
ThawRequestType requestType,
address serviceProvider,
address verifier,
address owner
) external view override returns (ILinkedList.List memory) {
return _getThawRequestList(requestType, serviceProvider, verifier, owner);
}
/// @inheritdoc IHorizonStakingBase
function getThawedTokens(
ThawRequestType requestType,
address serviceProvider,
address verifier,
address owner
) external view override returns (uint256) {
ILinkedList.List storage thawRequestList = _getThawRequestList(requestType, serviceProvider, verifier, owner);
if (thawRequestList.count == 0) {
return 0;
}
uint256 thawedTokens = 0;
Provision storage prov = _provisions[serviceProvider][verifier];
uint256 tokensThawing = prov.tokensThawing;
uint256 sharesThawing = prov.sharesThawing;
bytes32 thawRequestId = thawRequestList.head;
while (thawRequestId != bytes32(0)) {
ThawRequest storage thawRequest = _getThawRequest(requestType, thawRequestId);
if (thawRequest.thawingNonce == prov.thawingNonce) {
if (thawRequest.thawingUntil <= block.timestamp) {
// sharesThawing cannot be zero if there is a valid thaw request so the next division is safe
uint256 tokens = (thawRequest.shares * tokensThawing) / sharesThawing;
tokensThawing = tokensThawing - tokens;
sharesThawing = sharesThawing - thawRequest.shares;
thawedTokens = thawedTokens + tokens;
} else {
break;
}
}
thawRequestId = thawRequest.nextRequest;
}
return thawedTokens;
}
/// @inheritdoc IHorizonStakingBase
function getMaxThawingPeriod() external view override returns (uint64) {
return _maxThawingPeriod;
}
/// @inheritdoc IHorizonStakingBase
function isAllowedLockedVerifier(address verifier) external view returns (bool) {
return _allowedLockedVerifiers[verifier];
}
/// @inheritdoc IHorizonStakingBase
function isDelegationSlashingEnabled() external view returns (bool) {
return _delegationSlashingEnabled;
}
/**
* @notice Deposit tokens into the service provider stake.
* @dev TRANSITION PERIOD: After transition period move to IHorizonStakingMain. Temporarily it
* needs to be here since it's used by both {HorizonStaking} and {HorizonStakingExtension}.
*
* Emits a {HorizonStakeDeposited} event.
* @param _serviceProvider The address of the service provider.
* @param _tokens The amount of tokens to deposit.
*/
function _stake(address _serviceProvider, uint256 _tokens) internal {
_serviceProviders[_serviceProvider].tokensStaked = _serviceProviders[_serviceProvider].tokensStaked + _tokens;
emit HorizonStakeDeposited(_serviceProvider, _tokens);
}
/**
* @notice Gets the service provider's idle stake which is the stake that is not being
* used for any provision. Note that this only includes service provider's self stake.
* @dev Note that the calculation considers tokens that were locked in the legacy staking contract.
* @dev TRANSITION PERIOD: update the calculation after the transition period.
* @param _serviceProvider The address of the service provider.
* @return The amount of tokens that are idle.
*/
function _getIdleStake(address _serviceProvider) internal view returns (uint256) {
uint256 tokensUsed = _serviceProviders[_serviceProvider].tokensProvisioned +
_serviceProviders[_serviceProvider].__DEPRECATED_tokensAllocated +
_serviceProviders[_serviceProvider].__DEPRECATED_tokensLocked;
uint256 tokensStaked = _serviceProviders[_serviceProvider].tokensStaked;
return tokensStaked > tokensUsed ? tokensStaked - tokensUsed : 0;
}
/**
* @notice Gets the details of delegation pool.
* @dev Note that this function handles the special case where the verifier is the subgraph data service,
* where the pools are stored in the legacy mapping.
* @param _serviceProvider The address of the service provider.
* @param _verifier The address of the verifier.
* @return The delegation pool details.
*/
function _getDelegationPool(
address _serviceProvider,
address _verifier
) internal view returns (DelegationPoolInternal storage) {
if (_verifier == SUBGRAPH_DATA_SERVICE_ADDRESS) {
return _legacyDelegationPools[_serviceProvider];
} else {
return _delegationPools[_serviceProvider][_verifier];
}
}
/**
* @notice Gets the service provider's tokens available in a provision.
* @dev Calculated as the tokens available minus the tokens thawing.
* @param _serviceProvider The address of the service provider.
* @param _verifier The address of the verifier.
* @return The amount of tokens available.
*/
function _getProviderTokensAvailable(address _serviceProvider, address _verifier) internal view returns (uint256) {
return _provisions[_serviceProvider][_verifier].tokens - _provisions[_serviceProvider][_verifier].tokensThawing;
}
/**
* @notice Retrieves the next thaw request for a provision.
* @param _thawRequestId The ID of the current thaw request.
* @return The ID of the next thaw request in the list.
*/
function _getNextProvisionThawRequest(bytes32 _thawRequestId) internal view returns (bytes32) {
return _thawRequests[ThawRequestType.Provision][_thawRequestId].nextRequest;
}
/**
* @notice Retrieves the next thaw request for a delegation.
* @param _thawRequestId The ID of the current thaw request.
* @return The ID of the next thaw request in the list.
*/
function _getNextDelegationThawRequest(bytes32 _thawRequestId) internal view returns (bytes32) {
return _thawRequests[ThawRequestType.Delegation][_thawRequestId].nextRequest;
}
/**
* @notice Retrieves the thaw request list for the given request type.
* @dev Uses the `ThawRequestType` to determine which mapping to access.
* Reverts if the request type is unknown.
* @param _requestType The type of thaw request (Provision or Delegation).
* @param _serviceProvider The address of the service provider.
* @param _verifier The address of the verifier.
* @param _owner The address of the owner of the thaw request.
* @return The linked list of thaw requests for the specified request type.
*/
function _getThawRequestList(
ThawRequestType _requestType,
address _serviceProvider,
address _verifier,
address _owner
) internal view returns (ILinkedList.List storage) {
return _thawRequestLists[_requestType][_serviceProvider][_verifier][_owner];
}
/**
* @notice Retrieves a specific thaw request for the given request type.
* @dev Uses the `ThawRequestType` to determine which mapping to access.
* @param _requestType The type of thaw request (Provision or Delegation).
* @param _thawRequestId The unique ID of the thaw request.
* @return The thaw request data for the specified request type and ID.
*/
function _getThawRequest(
ThawRequestType _requestType,
bytes32 _thawRequestId
) internal view returns (IHorizonStakingTypes.ThawRequest storage) {
return _thawRequests[_requestType][_thawRequestId];
}
/**
* @notice Determines the correct callback function for `getNextItem` based on the request type.
* @param _requestType The type of thaw request (Provision or Delegation).
* @return A function pointer to the appropriate `getNextItem` callback.
*/
function _getNextThawRequest(
ThawRequestType _requestType
) internal pure returns (function(bytes32) view returns (bytes32)) {
if (_requestType == ThawRequestType.Provision) {
return _getNextProvisionThawRequest;
} else if (_requestType == ThawRequestType.Delegation) {
return _getNextDelegationThawRequest;
} else {
revert HorizonStakingInvalidThawRequestType();
}
}
/**
* @notice Gets the delegator's tokens available in a provision.
* @dev Calculated as the tokens available minus the tokens thawing.
* @param _serviceProvider The address of the service provider.
* @param _verifier The address of the verifier.
* @return The amount of tokens available.
*/
function _getDelegatedTokensAvailable(address _serviceProvider, address _verifier) private view returns (uint256) {
DelegationPoolInternal storage poolInternal = _getDelegationPool(_serviceProvider, _verifier);
return poolInternal.tokens - poolInternal.tokensThawing;
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol";
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IHorizonStakingExtension } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol";
import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol";
import { TokenUtils } from "@graphprotocol/contracts/contracts/utils/TokenUtils.sol";
import { MathUtils } from "../libraries/MathUtils.sol";
import { ExponentialRebates } from "./libraries/ExponentialRebates.sol";
import { PPMMath } from "../libraries/PPMMath.sol";
import { HorizonStakingBase } from "./HorizonStakingBase.sol";
/**
* @title Horizon Staking extension contract
* @notice The {HorizonStakingExtension} contract implements the legacy functionality required to support the transition
* to the Horizon Staking contract. It allows indexers to close allocations and collect pending query fees, but it
* does not allow for the creation of new allocations. This should allow indexers to migrate to a subgraph data service
* without losing rewards or having service interruptions.
* @dev TRANSITION PERIOD: Once the transition period passes this contract can be removed (note that an upgrade to the
* RewardsManager will also be required). It's expected the transition period to last for at least a full allocation cycle
* (28 epochs).
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension {
using TokenUtils for IGraphToken;
using PPMMath for uint256;
/**
* @dev Check if the caller is the slasher.
*/
modifier onlySlasher() {
require(__DEPRECATED_slashers[msg.sender], "!slasher");
_;
}
/**
* @dev The staking contract is upgradeable however we still use the constructor to set
* a few immutable variables.
* @param controller The address of the Graph controller contract.
* @param subgraphDataServiceAddress The address of the subgraph data service.
*/
constructor(
address controller,
address subgraphDataServiceAddress
) HorizonStakingBase(controller, subgraphDataServiceAddress) {}
/// @inheritdoc IHorizonStakingExtension
function closeAllocation(address allocationID, bytes32 poi) external override notPaused {
_closeAllocation(allocationID, poi);
}
/// @inheritdoc IHorizonStakingExtension
function collect(uint256 tokens, address allocationID) external override notPaused {
// Allocation identifier validation
require(allocationID != address(0), "!alloc");
// Allocation must exist
AllocationState allocState = _getAllocationState(allocationID);
require(allocState != AllocationState.Null, "!collect");
// If the query fees are zero, we don't want to revert
// but we also don't need to do anything, so just return
if (tokens == 0) {
return;
}
Allocation storage alloc = __DEPRECATED_allocations[allocationID];
bytes32 subgraphDeploymentID = alloc.subgraphDeploymentID;
uint256 queryFees = tokens; // Tokens collected from the channel
uint256 protocolTax = 0; // Tokens burnt as protocol tax
uint256 curationFees = 0; // Tokens distributed to curators as curation fees
uint256 queryRebates = 0; // Tokens to distribute to indexer
uint256 delegationRewards = 0; // Tokens to distribute to delegators
{
// -- Pull tokens from the sender --
_graphToken().pullTokens(msg.sender, queryFees);
// -- Collect protocol tax --
protocolTax = _collectTax(queryFees, __DEPRECATED_protocolPercentage);
queryFees = queryFees - protocolTax;
// -- Collect curation fees --
// Only if the subgraph deployment is curated
curationFees = _collectCurationFees(subgraphDeploymentID, queryFees, __DEPRECATED_curationPercentage);
queryFees = queryFees - curationFees;
// -- Process rebate reward --
// Using accumulated fees and subtracting previously distributed rebates
// allows for multiple vouchers to be collected while following the rebate formula
alloc.collectedFees = alloc.collectedFees + queryFees;
// No rebates if indexer has no stake or if lambda is zero
uint256 newRebates = (alloc.tokens == 0 || __DEPRECATED_lambdaNumerator == 0)
? 0
: ExponentialRebates.exponentialRebates(
alloc.collectedFees,
alloc.tokens,
__DEPRECATED_alphaNumerator,
__DEPRECATED_alphaDenominator,
__DEPRECATED_lambdaNumerator,
__DEPRECATED_lambdaDenominator
);
// -- Ensure rebates to distribute are within bounds --
// Indexers can become under or over rebated if rebate parameters (alpha, lambda)
// change between successive collect calls for the same allocation
// Ensure rebates to distribute are not negative (indexer is over-rebated)
queryRebates = MathUtils.diffOrZero(newRebates, alloc.distributedRebates);
// Ensure rebates to distribute are not greater than available (indexer is under-rebated)
queryRebates = MathUtils.min(queryRebates, queryFees);
// -- Burn rebates remanent --
_graphToken().burnTokens(queryFees - queryRebates);
// -- Distribute rebates --
if (queryRebates > 0) {
alloc.distributedRebates = alloc.distributedRebates + queryRebates;
// -- Collect delegation rewards into the delegation pool --
delegationRewards = _collectDelegationQueryRewards(alloc.indexer, queryRebates);
queryRebates = queryRebates - delegationRewards;
// -- Transfer or restake rebates --
_sendRewards(queryRebates, alloc.indexer, __DEPRECATED_rewardsDestination[alloc.indexer] == address(0));
}
}
emit RebateCollected(
msg.sender,
alloc.indexer,
subgraphDeploymentID,
allocationID,
_graphEpochManager().currentEpoch(),
tokens,
protocolTax,
curationFees,
queryFees,
queryRebates,
delegationRewards
);
}
/// @inheritdoc IHorizonStakingExtension
function legacySlash(
address indexer,
uint256 tokens,
uint256 reward,
address beneficiary
) external override onlySlasher notPaused {
ServiceProviderInternal storage indexerStake = _serviceProviders[indexer];
// Only able to slash a non-zero number of tokens
require(tokens > 0, "!tokens");
// Rewards comes from tokens slashed balance
require(tokens >= reward, "rewards>slash");
// Cannot slash stake of an indexer without any or enough stake
require(indexerStake.tokensStaked > 0, "!stake");
require(tokens <= indexerStake.tokensStaked, "slash>stake");
// Validate beneficiary of slashed tokens
require(beneficiary != address(0), "!beneficiary");
// Slashing tokens that are already provisioned would break provision accounting, we need to limit
// the slash amount. This can be compensated for, by slashing with the main slash function if needed.
uint256 slashableStake = indexerStake.tokensStaked - indexerStake.tokensProvisioned;
if (slashableStake == 0) {
emit StakeSlashed(indexer, 0, 0, beneficiary);
return;
}
if (tokens > slashableStake) {
reward = (reward * slashableStake) / tokens;
tokens = slashableStake;
}
// Slashing more tokens than freely available (over allocation condition)
// Unlock locked tokens to avoid the indexer to withdraw them
uint256 tokensUsed = indexerStake.__DEPRECATED_tokensAllocated + indexerStake.__DEPRECATED_tokensLocked;
uint256 tokensAvailable = tokensUsed > indexerStake.tokensStaked ? 0 : indexerStake.tokensStaked - tokensUsed;
if (tokens > tokensAvailable && indexerStake.__DEPRECATED_tokensLocked > 0) {
uint256 tokensOverAllocated = tokens - tokensAvailable;
uint256 tokensToUnlock = MathUtils.min(tokensOverAllocated, indexerStake.__DEPRECATED_tokensLocked);
indexerStake.__DEPRECATED_tokensLocked = indexerStake.__DEPRECATED_tokensLocked - tokensToUnlock;
if (indexerStake.__DEPRECATED_tokensLocked == 0) {
indexerStake.__DEPRECATED_tokensLockedUntil = 0;
}
}
// Remove tokens to slash from the stake
indexerStake.tokensStaked = indexerStake.tokensStaked - tokens;
// -- Interactions --
// Set apart the reward for the beneficiary and burn remaining slashed stake
_graphToken().burnTokens(tokens - reward);
// Give the beneficiary a reward for slashing
_graphToken().pushTokens(beneficiary, reward);
emit StakeSlashed(indexer, tokens, reward, beneficiary);
}
/// @inheritdoc IHorizonStakingExtension
function isAllocation(address allocationID) external view override returns (bool) {
return _getAllocationState(allocationID) != AllocationState.Null;
}
/// @inheritdoc IHorizonStakingExtension
function getAllocation(address allocationID) external view override returns (Allocation memory) {
return __DEPRECATED_allocations[allocationID];
}
/// @inheritdoc IRewardsIssuer
function getAllocationData(
address allocationID
) external view override returns (bool, address, bytes32, uint256, uint256, uint256) {
Allocation memory allo = __DEPRECATED_allocations[allocationID];
bool isActive = _getAllocationState(allocationID) == AllocationState.Active;
return (isActive, allo.indexer, allo.subgraphDeploymentID, allo.tokens, allo.accRewardsPerAllocatedToken, 0);
}
/// @inheritdoc IHorizonStakingExtension
function getAllocationState(address allocationID) external view override returns (AllocationState) {
return _getAllocationState(allocationID);
}
/// @inheritdoc IRewardsIssuer
function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentID) external view override returns (uint256) {
return __DEPRECATED_subgraphAllocations[subgraphDeploymentID];
}
/// @inheritdoc IHorizonStakingExtension
function getIndexerStakedTokens(address indexer) external view override returns (uint256) {
return _serviceProviders[indexer].tokensStaked;
}
/// @inheritdoc IHorizonStakingExtension
function getSubgraphService() external view override returns (address) {
return SUBGRAPH_DATA_SERVICE_ADDRESS;
}
/// @inheritdoc IHorizonStakingExtension
function hasStake(address indexer) external view override returns (bool) {
return _serviceProviders[indexer].tokensStaked > 0;
}
/// @inheritdoc IHorizonStakingExtension
function __DEPRECATED_getThawingPeriod() external view returns (uint64) {
return __DEPRECATED_thawingPeriod;
}
/// @inheritdoc IHorizonStakingExtension
function isOperator(address operator, address serviceProvider) public view override returns (bool) {
return _legacyOperatorAuth[serviceProvider][operator];
}
/**
* @dev Collect tax to burn for an amount of tokens.
* @param _tokens Total tokens received used to calculate the amount of tax to collect
* @param _percentage Percentage of tokens to burn as tax
* @return Amount of tax charged
*/
function _collectTax(uint256 _tokens, uint256 _percentage) private returns (uint256) {
uint256 tax = _tokens.mulPPMRoundUp(_percentage);
_graphToken().burnTokens(tax); // Burn tax if any
return tax;
}
/**
* @dev Triggers an update of rewards due to a change in allocations.
* @param _subgraphDeploymentID Subgraph deployment updated
*/
function _updateRewards(bytes32 _subgraphDeploymentID) private {
_graphRewardsManager().onSubgraphAllocationUpdate(_subgraphDeploymentID);
}
/**
* @dev Assign rewards for the closed allocation to indexer and delegators.
* @param _allocationID Allocation
* @param _indexer Address of the indexer that did the allocation
*/
function _distributeRewards(address _allocationID, address _indexer) private {
// Automatically triggers update of rewards snapshot as allocation will change
// after this call. Take rewards mint tokens for the Staking contract to distribute
// between indexer and delegators
uint256 totalRewards = _graphRewardsManager().takeRewards(_allocationID);
if (totalRewards == 0) {
return;
}
// Calculate delegation rewards and add them to the delegation pool
uint256 delegationRewards = _collectDelegationIndexingRewards(_indexer, totalRewards);
uint256 indexerRewards = totalRewards - delegationRewards;
// Send the indexer rewards
_sendRewards(indexerRewards, _indexer, __DEPRECATED_rewardsDestination[_indexer] == address(0));
}
/**
* @dev Send rewards to the appropriate destination.
* @param _tokens Number of rewards tokens
* @param _beneficiary Address of the beneficiary of rewards
* @param _restake Whether to restake or not
*/
function _sendRewards(uint256 _tokens, address _beneficiary, bool _restake) private {
if (_tokens == 0) return;
if (_restake) {
// Restake to place fees into the indexer stake
_stake(_beneficiary, _tokens);
} else {
// Transfer funds to the beneficiary's designated rewards destination if set
address destination = __DEPRECATED_rewardsDestination[_beneficiary];
_graphToken().pushTokens(destination == address(0) ? _beneficiary : destination, _tokens);
}
}
/**
* @dev Close an allocation and free the staked tokens.
* @param _allocationID The allocation identifier
* @param _poi Proof of indexing submitted for the allocated period
*/
function _closeAllocation(address _allocationID, bytes32 _poi) private {
// Allocation must exist and be active
AllocationState allocState = _getAllocationState(_allocationID);
require(allocState == AllocationState.Active, "!active");
// Get allocation
Allocation memory alloc = __DEPRECATED_allocations[_allocationID];
// Validate that an allocation cannot be closed before one epoch
alloc.closedAtEpoch = _graphEpochManager().currentEpoch();
uint256 epochs = MathUtils.diffOrZero(alloc.closedAtEpoch, alloc.createdAtEpoch);
// Indexer or operator can close an allocation
// Anyone is allowed to close ONLY under two concurrent conditions
// - After maxAllocationEpochs passed
// - When the allocation is for non-zero amount of tokens
bool isIndexerOrOperator = msg.sender == alloc.indexer || isOperator(msg.sender, alloc.indexer);
if (epochs <= __DEPRECATED_maxAllocationEpochs || alloc.tokens == 0) {
require(isIndexerOrOperator, "!auth");
}
// -- Rewards Distribution --
// Process non-zero-allocation rewards tracking
if (alloc.tokens > 0) {
// Distribute rewards if proof of indexing was presented by the indexer or operator
if (isIndexerOrOperator && _poi != 0 && epochs > 0) {
_distributeRewards(_allocationID, alloc.indexer);
} else {
_updateRewards(alloc.subgraphDeploymentID);
}
// Free allocated tokens from use
_serviceProviders[alloc.indexer].__DEPRECATED_tokensAllocated =
_serviceProviders[alloc.indexer].__DEPRECATED_tokensAllocated - alloc.tokens;
// Track total allocations per subgraph
// Used for rewards calculations
__DEPRECATED_subgraphAllocations[alloc.subgraphDeploymentID] =
__DEPRECATED_subgraphAllocations[alloc.subgraphDeploymentID] - alloc.tokens;
}
// Close the allocation
// Note that this breaks CEI pattern. We update after the rewards distribution logic as it expects the allocation
// to still be active. There shouldn't be reentrancy risk here as all internal calls are to trusted contracts.
__DEPRECATED_allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch;
emit AllocationClosed(
alloc.indexer,
alloc.subgraphDeploymentID,
alloc.closedAtEpoch,
alloc.tokens,
_allocationID,
msg.sender,
_poi,
!isIndexerOrOperator
);
}
/**
* @dev Collect the delegation rewards for query fees.
* This function will assign the collected fees to the delegation pool.
* @param _indexer Indexer to which the tokens to distribute are related
* @param _tokens Total tokens received used to calculate the amount of fees to collect
* @return Amount of delegation rewards
*/
function _collectDelegationQueryRewards(address _indexer, uint256 _tokens) private returns (uint256) {
uint256 delegationRewards = 0;
DelegationPoolInternal storage pool = _legacyDelegationPools[_indexer];
if (pool.tokens > 0 && uint256(pool.__DEPRECATED_queryFeeCut).isValidPPM()) {
uint256 indexerCut = uint256(pool.__DEPRECATED_queryFeeCut).mulPPM(_tokens);
delegationRewards = _tokens - indexerCut;
pool.tokens = pool.tokens + delegationRewards;
}
return delegationRewards;
}
/**
* @dev Collect the delegation rewards for indexing.
* This function will assign the collected fees to the delegation pool.
* @param _indexer Indexer to which the tokens to distribute are related
* @param _tokens Total tokens received used to calculate the amount of fees to collect
* @return Amount of delegation rewards
*/
function _collectDelegationIndexingRewards(address _indexer, uint256 _tokens) private returns (uint256) {
uint256 delegationRewards = 0;
DelegationPoolInternal storage pool = _legacyDelegationPools[_indexer];
if (pool.tokens > 0 && uint256(pool.__DEPRECATED_indexingRewardCut).isValidPPM()) {
uint256 indexerCut = uint256(pool.__DEPRECATED_indexingRewardCut).mulPPM(_tokens);
delegationRewards = _tokens - indexerCut;
pool.tokens = pool.tokens + delegationRewards;
}
return delegationRewards;
}
/**
* @dev Collect the curation fees for a subgraph deployment from an amount of tokens.
* This function transfer curation fees to the Curation contract by calling Curation.collect
* @param _subgraphDeploymentID Subgraph deployment to which the curation fees are related
* @param _tokens Total tokens received used to calculate the amount of fees to collect
* @param _curationCut Percentage of tokens to collect as fees
* @return Amount of curation fees
*/
function _collectCurationFees(
bytes32 _subgraphDeploymentID,
uint256 _tokens,
uint256 _curationCut
) private returns (uint256) {
if (_tokens == 0) {
return 0;
}
ICuration curation = _graphCuration();
bool isCurationEnabled = _curationCut > 0 && address(curation) != address(0);
if (isCurationEnabled && curation.isCurated(_subgraphDeploymentID)) {
uint256 curationFees = _tokens.mulPPMRoundUp(_curationCut);
if (curationFees > 0) {
// Transfer and call collect()
// This function transfer tokens to a trusted protocol contracts
// Then we call collect() to do the transfer Bookkeeping
_graphRewardsManager().onSubgraphSignalUpdate(_subgraphDeploymentID);
_graphToken().pushTokens(address(curation), curationFees);
curation.collect(_subgraphDeploymentID, curationFees);
}
return curationFees;
}
return 0;
}
/**
* @dev Return the current state of an allocation
* @param _allocationID Allocation identifier
* @return AllocationState enum with the state of the allocation
*/
function _getAllocationState(address _allocationID) private view returns (AllocationState) {
Allocation storage alloc = __DEPRECATED_allocations[_allocationID];
if (alloc.indexer == address(0)) {
return AllocationState.Null;
}
if (alloc.createdAtEpoch != 0 && alloc.closedAtEpoch == 0) {
return AllocationState.Active;
}
return AllocationState.Closed;
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IHorizonStakingExtension } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol";
import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol";
/* solhint-disable max-states-count */
/**
* @title HorizonStakingV1Storage
* @notice This contract holds all the storage variables for the Staking contract.
* @dev Deprecated variables are kept to support the transition to Horizon Staking.
* They can eventually be collapsed into a single storage slot.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract HorizonStakingV1Storage {
// -- Staking --
/// @dev Minimum amount of tokens an indexer needs to stake.
/// Deprecated, now enforced by each data service (verifier)
uint256 internal __DEPRECATED_minimumIndexerStake;
/// @dev Time in blocks to unstake
/// Deprecated, now enforced by each data service (verifier)
uint32 internal __DEPRECATED_thawingPeriod; // in blocks
/// @dev Percentage of fees going to curators
/// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
/// Deprecated, now enforced by each data service (verifier)
uint32 internal __DEPRECATED_curationPercentage;
/// @dev Percentage of fees burned as protocol fee
/// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
/// Deprecated, now enforced by each data service (verifier)
uint32 internal __DEPRECATED_protocolPercentage;
/// @dev Period for allocation to be finalized
/// Deprecated with exponential rebates.
uint32 private __DEPRECATED_channelDisputeEpochs;
/// @dev Maximum allocation time.
/// Deprecated, allocations now live on the subgraph service contract.
uint32 internal __DEPRECATED_maxAllocationEpochs;
/// @dev Rebate alpha numerator
/// Originally used for Cobb-Douglas rebates, now used for exponential rebates
/// Deprecated, any rebate mechanism is now applied on the subgraph data service.
uint32 internal __DEPRECATED_alphaNumerator;
/// @dev Rebate alpha denominator
/// Originally used for Cobb-Douglas rebates, now used for exponential rebates
/// Deprecated, any rebate mechanism is now applied on the subgraph data service.
uint32 internal __DEPRECATED_alphaDenominator;
/// @dev Service providers details, tracks stake utilization.
mapping(address serviceProvider => IHorizonStakingTypes.ServiceProviderInternal details) internal _serviceProviders;
/// @dev Allocation details.
/// Deprecated, now applied on the subgraph data service
mapping(address allocationId => IHorizonStakingExtension.Allocation allocation) internal __DEPRECATED_allocations;
/// @dev Subgraph allocations, tracks the tokens allocated to a subgraph deployment
/// Deprecated, now applied on the SubgraphService
mapping(bytes32 subgraphDeploymentId => uint256 tokens) internal __DEPRECATED_subgraphAllocations;
/// @dev Rebate pool details per epoch
/// Deprecated with exponential rebates.
mapping(uint256 epoch => uint256 rebates) private __DEPRECATED_rebates;
// -- Slashing --
/// @dev List of addresses allowed to slash stakes
/// Deprecated, now each verifier can slash the corresponding provision.
mapping(address slasher => bool allowed) internal __DEPRECATED_slashers;
// -- Delegation --
/// @dev Delegation capacity multiplier defined by the delegation ratio
/// Deprecated, enforced by each data service as needed.
uint32 internal __DEPRECATED_delegationRatio;
/// @dev Time in blocks an indexer needs to wait to change delegation parameters
/// Deprecated, enforced by each data service as needed.
uint32 internal __DEPRECATED_delegationParametersCooldown;
/// @dev Time in epochs a delegator needs to wait to withdraw delegated stake
/// Deprecated, now only enforced during a transition period
uint32 internal __DEPRECATED_delegationUnbondingPeriod;
/// @dev Percentage of tokens to tax a delegation deposit
/// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
/// Deprecated, no tax is applied now.
uint32 internal __DEPRECATED_delegationTaxPercentage;
/// @dev Delegation pools (legacy).
/// Only used when the verifier is the subgraph data service.
mapping(address serviceProvider => IHorizonStakingTypes.DelegationPoolInternal delegationPool)
internal _legacyDelegationPools;
// -- Operators --
/// @dev Operator allow list (legacy)
/// Only used when the verifier is the subgraph data service.
mapping(address serviceProvider => mapping(address legacyOperator => bool authorized)) internal _legacyOperatorAuth;
// -- Asset Holders --
/// @dev Asset holder allow list
/// Deprecated with permissionless payers
mapping(address assetHolder => bool allowed) private __DEPRECATED_assetHolders;
/// @dev Destination of accrued indexing rewards
/// Deprecated, defined by each data service as needed
mapping(address serviceProvider => address rewardsDestination) internal __DEPRECATED_rewardsDestination;
/// @dev Address of the counterpart Staking contract on L1/L2
/// Deprecated, transfer tools no longer enabled.
address internal __DEPRECATED_counterpartStakingAddress;
/// @dev Address of the StakingExtension implementation
/// This is now an immutable variable to save some gas.
address internal __DEPRECATED_extensionImpl;
/// @dev Rebate lambda numerator for exponential rebates
/// Deprecated, any rebate mechanism is now applied on the subgraph data service.
uint32 internal __DEPRECATED_lambdaNumerator;
/// @dev Rebate lambda denominator for exponential rebates
/// Deprecated, any rebate mechanism is now applied on the subgraph data service.
uint32 internal __DEPRECATED_lambdaDenominator;
// -- Horizon Staking --
/// @dev Maximum thawing period, in seconds, for a provision
/// Note that to protect delegation from being unfairly locked this should be set to a sufficiently low value
/// Additionally note that setting this to a high enough value could lead to overflow when calculating thawing until
/// dates. For practical purposes this should not be an issue but we recommend using a value like 1e18 to represent
/// "infinite thawing" if that is the intent.
uint64 internal _maxThawingPeriod;
/// @dev Provisions from each service provider for each data service
mapping(address serviceProvider => mapping(address verifier => IHorizonStakingTypes.Provision provision))
internal _provisions;
/// @dev Delegation fee cuts for each service provider on each provision, by fee type:
/// This is the effective delegator fee cuts for each (data-service-defined) fee type (e.g. indexing fees, query fees).
/// This is in PPM and is the cut taken by the service provider from the fees that correspond to delegators.
/// (based on stake vs delegated stake proportion).
/// The cuts are applied in GraphPayments so apply to all data services that use it.
mapping(address serviceProvider => mapping(address verifier => mapping(IGraphPayments.PaymentTypes paymentType => uint256 feeCut)))
internal _delegationFeeCut;
/// @dev Thaw requests
/// Details for each thawing operation in the staking contract (for both service providers and delegators).
mapping(IHorizonStakingTypes.ThawRequestType thawRequestType => mapping(bytes32 thawRequestId => IHorizonStakingTypes.ThawRequest thawRequest))
internal _thawRequests;
/// @dev Thaw request lists
/// Metadata defining linked lists of thaw requests for each service provider or delegator (owner)
mapping(IHorizonStakingTypes.ThawRequestType thawRequestType => mapping(address serviceProvider => mapping(address verifier => mapping(address owner => ILinkedList.List list))))
internal _thawRequestLists;
/// @dev Operator allow list
/// Used for all verifiers except the subgraph data service.
mapping(address serviceProvider => mapping(address verifier => mapping(address operator => bool authorized)))
internal _operatorAuth;
/// @dev Flag to enable or disable delegation slashing
bool internal _delegationSlashingEnabled;
/// @dev Delegation pools for each service provider and verifier
mapping(address serviceProvider => mapping(address verifier => IHorizonStakingTypes.DelegationPoolInternal delegationPool))
internal _delegationPools;
/// @dev Allowed verifiers for locked provisions (i.e. from GraphTokenLockWallets)
// Verifiers are whitelisted to ensure locked tokens cannot escape using an arbitrary verifier.
mapping(address verifier => bool allowed) internal _allowedLockedVerifiers;
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { LibFixedMath } from "../../libraries/LibFixedMath.sol";
/**
* @title ExponentialRebates library
* @notice A library to compute query fee rebates using an exponential formula
* @dev This is only used for backwards compatibility in HorizonStaking, and should
* be removed after the transition period.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
library ExponentialRebates {
/// @dev Maximum value of the exponent for which to compute the exponential before clamping to zero.
uint32 private constant MAX_EXPONENT = 15;
/// @dev The exponential formula used to compute fee-based rewards for
/// staking pools in a given epoch. This function does not perform
/// bounds checking on the inputs, but the following conditions
/// need to be true:
/// 0 <= alphaNumerator / alphaDenominator <= 1
/// 0 < lambdaNumerator / lambdaDenominator
/// The exponential rebates function has the form:
/// `(1 - alpha * exp ^ (-lambda * stake / fees)) * fees`
/// @param fees Fees generated by indexer in the staking pool.
/// @param stake Stake attributed to the indexer in the staking pool.
/// @param alphaNumerator Numerator of `alpha` in the rebates function.
/// @param alphaDenominator Denominator of `alpha` in the rebates function.
/// @param lambdaNumerator Numerator of `lambda` in the rebates function.
/// @param lambdaDenominator Denominator of `lambda` in the rebates function.
/// @return rewards Rewards owed to the staking pool.
function exponentialRebates(
uint256 fees,
uint256 stake,
uint32 alphaNumerator,
uint32 alphaDenominator,
uint32 lambdaNumerator,
uint32 lambdaDenominator
) external pure returns (uint256) {
// If alpha is zero indexer gets 100% fees rebate
int256 alpha = LibFixedMath.toFixed(int32(alphaNumerator), int32(alphaDenominator));
if (alpha == 0) {
return fees;
}
// No rebates if no fees...
if (fees == 0) {
return 0;
}
// Award all fees as rebate if the exponent is too large
int256 lambda = LibFixedMath.toFixed(int32(lambdaNumerator), int32(lambdaDenominator));
int256 exponent = LibFixedMath.mulDiv(lambda, int256(stake), int256(fees));
if (LibFixedMath.toInteger(exponent) > int256(uint256(MAX_EXPONENT))) {
return fees;
}
// Compute `1 - alpha * exp ^(-exponent)`
int256 factor = LibFixedMath.sub(LibFixedMath.one(), LibFixedMath.mul(alpha, LibFixedMath.exp(-exponent)));
// Weight the fees by the factor
return LibFixedMath.uintMul(factor, fees);
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { GraphDirectory } from "../../utilities/GraphDirectory.sol";
/* solhint-disable var-name-mixedcase */
/**
* @title Graph Managed contract
* @dev The Managed contract provides an interface to interact with the Controller.
* For Graph Horizon this contract is mostly a shell that uses {GraphDirectory}, however since the {HorizonStaking}
* contract uses it we need to preserve the storage layout.
* Inspired by Livepeer: https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract Managed is GraphDirectory {
// -- State --
/// @notice Controller that manages this contract
address private __DEPRECATED_controller;
/// @dev Cache for the addresses of the contracts retrieved from the controller
mapping(bytes32 contractName => address contractAddress) private __DEPRECATED_addressCache;
/// @dev Gap for future storage variables
uint256[10] private __gap;
/**
* @notice Thrown when a protected function is called and the contract is paused.
*/
error ManagedIsPaused();
/**
* @notice Thrown when a the caller is not the expected controller address.
*/
error ManagedOnlyController();
/**
* @notice Thrown when a the caller is not the governor.
*/
error ManagedOnlyGovernor();
/**
* @dev Revert if the controller is paused
*/
modifier notPaused() {
require(!_graphController().paused(), ManagedIsPaused());
_;
}
/**
* @dev Revert if the caller is not the governor
*/
modifier onlyGovernor() {
require(msg.sender == _graphController().getGovernor(), ManagedOnlyGovernor());
_;
}
/**
* @dev Initialize the contract
* @param controller_ The address of the Graph controller contract.
*/
constructor(address controller_) GraphDirectory(controller_) {}
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.27;
import { IAuthorizable } from "@graphprotocol/interfaces/contracts/horizon/IAuthorizable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
/**
* @title Authorizable contract
* @dev Implements the {IAuthorizable} interface.
* @notice A mechanism to authorize signers to sign messages on behalf of an authorizer.
* Signers cannot be reused for different authorizers.
* @dev Contract uses "authorizeSignerProof" as the domain for signer proofs.
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
abstract contract Authorizable is IAuthorizable {
/// @notice The duration (in seconds) for which an authorization is thawing before it can be revoked
uint256 public immutable REVOKE_AUTHORIZATION_THAWING_PERIOD;
/// @notice Authorization details for authorizer-signer pairs
mapping(address signer => Authorization authorization) public authorizations;
/**
* @dev Revert if the caller has not authorized the signer
* @param signer The address of the signer
*/
modifier onlyAuthorized(address signer) {
_requireAuthorized(msg.sender, signer);
_;
}
/**
* @notice Constructs a new instance of the Authorizable contract.
* @param revokeAuthorizationThawingPeriod The duration (in seconds) for which an authorization is thawing before it can be revoked.
*/
constructor(uint256 revokeAuthorizationThawingPeriod) {
REVOKE_AUTHORIZATION_THAWING_PERIOD = revokeAuthorizationThawingPeriod;
}
/// @inheritdoc IAuthorizable
function authorizeSigner(address signer, uint256 proofDeadline, bytes calldata proof) external {
require(
authorizations[signer].authorizer == address(0),
AuthorizableSignerAlreadyAuthorized(
authorizations[signer].authorizer,
signer,
authorizations[signer].revoked
)
);
_verifyAuthorizationProof(proof, proofDeadline, signer);
authorizations[signer].authorizer = msg.sender;
emit SignerAuthorized(msg.sender, signer);
}
/// @inheritdoc IAuthorizable
function thawSigner(address signer) external onlyAuthorized(signer) {
authorizations[signer].thawEndTimestamp = block.timestamp + REVOKE_AUTHORIZATION_THAWING_PERIOD;
emit SignerThawing(msg.sender, signer, authorizations[signer].thawEndTimestamp);
}
/// @inheritdoc IAuthorizable
function cancelThawSigner(address signer) external onlyAuthorized(signer) {
require(authorizations[signer].thawEndTimestamp > 0, AuthorizableSignerNotThawing(signer));
uint256 thawEnd = authorizations[signer].thawEndTimestamp;
authorizations[signer].thawEndTimestamp = 0;
emit SignerThawCanceled(msg.sender, signer, thawEnd);
}
/// @inheritdoc IAuthorizable
function revokeAuthorizedSigner(address signer) external onlyAuthorized(signer) {
uint256 thawEndTimestamp = authorizations[signer].thawEndTimestamp;
require(thawEndTimestamp > 0, AuthorizableSignerNotThawing(signer));
require(thawEndTimestamp <= block.timestamp, AuthorizableSignerStillThawing(block.timestamp, thawEndTimestamp));
authorizations[signer].revoked = true;
emit SignerRevoked(msg.sender, signer);
}
/// @inheritdoc IAuthorizable
function getThawEnd(address signer) external view returns (uint256) {
return authorizations[signer].thawEndTimestamp;
}
/// @inheritdoc IAuthorizable
function isAuthorized(address authorizer, address signer) external view returns (bool) {
return _isAuthorized(authorizer, signer);
}
/**
* @notice Returns true if the signer is authorized by the authorizer
* @param _authorizer The address of the authorizer
* @param _signer The address of the signer
* @return true if the signer is authorized by the authorizer, false otherwise
*/
function _isAuthorized(address _authorizer, address _signer) internal view returns (bool) {
return (_authorizer != address(0) &&
authorizations[_signer].authorizer == _authorizer &&
!authorizations[_signer].revoked);
}
/**
* @notice Reverts if the authorizer has not authorized the signer
* @param _authorizer The address of the authorizer
* @param _signer The address of the signer
*/
function _requireAuthorized(address _authorizer, address _signer) internal view {
require(_isAuthorized(_authorizer, _signer), AuthorizableSignerNotAuthorized(_authorizer, _signer));
}
/**
* @notice Verify the authorization proof provided by the authorizer
* @param _proof The proof provided by the authorizer
* @param _proofDeadline The deadline by which the proof must be verified
* @param _signer The authorization recipient
*/
function _verifyAuthorizationProof(bytes calldata _proof, uint256 _proofDeadline, address _signer) private view {
// Check that the proofDeadline has not passed
require(
_proofDeadline > block.timestamp,
AuthorizableInvalidSignerProofDeadline(_proofDeadline, block.timestamp)
);
// Generate the message hash
bytes32 messageHash = keccak256(
abi.encodePacked(block.chainid, address(this), "authorizeSignerProof", _proofDeadline, msg.sender)
);
// Generate the allegedly signed digest
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(messageHash);
// Verify that the recovered signer matches the to be authorized signer
require(ECDSA.recover(digest, _proof) == _signer, AuthorizableInvalidSignerProof());
}
}// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.27;
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol";
import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol";
import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol";
import { IController } from "@graphprotocol/interfaces/contracts/contracts/governance/IController.sol";
import { IEpochManager } from "@graphprotocol/interfaces/contracts/contracts/epochs/IEpochManager.sol";
import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol";
import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol";
import { IGraphProxyAdmin } from "@graphprotocol/interfaces/contracts/contracts/upgrades/IGraphProxyAdmin.sol";
import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol";
/**
* @title GraphDirectory contract
* @notice This contract is meant to be inherited by other contracts that
* need to keep track of the addresses in Graph Horizon contracts.
* It fetches the addresses from the Controller supplied during construction,
* and uses immutable variables to minimize gas costs.
*/
abstract contract GraphDirectory {
// -- Graph Horizon contracts --
/// @notice The Graph Token contract address
IGraphToken private immutable GRAPH_TOKEN;
/// @notice The Horizon Staking contract address
IHorizonStaking private immutable GRAPH_STAKING;
/// @notice The Graph Payments contract address
IGraphPayments private immutable GRAPH_PAYMENTS;
/// @notice The Payments Escrow contract address
IPaymentsEscrow private immutable GRAPH_PAYMENTS_ESCROW;
// -- Graph periphery contracts --
/// @notice The Graph Controller contract address
IController private immutable GRAPH_CONTROLLER;
/// @notice The Epoch Manager contract address
IEpochManager private immutable GRAPH_EPOCH_MANAGER;
/// @notice The Rewards Manager contract address
IRewardsManager private immutable GRAPH_REWARDS_MANAGER;
/// @notice The Token Gateway contract address
ITokenGateway private immutable GRAPH_TOKEN_GATEWAY;
/// @notice The Graph Proxy Admin contract address
IGraphProxyAdmin private immutable GRAPH_PROXY_ADMIN;
// -- Legacy Graph contracts --
// These are required for backwards compatibility on HorizonStakingExtension
// TRANSITION PERIOD: remove these once HorizonStakingExtension is removed
/// @notice The Curation contract address
ICuration private immutable GRAPH_CURATION;
/**
* @notice Emitted when the GraphDirectory is initialized
* @param graphToken The Graph Token contract address
* @param graphStaking The Horizon Staking contract address
* @param graphPayments The Graph Payments contract address
* @param graphEscrow The Payments Escrow contract address
* @param graphController The Graph Controller contract address
* @param graphEpochManager The Epoch Manager contract address
* @param graphRewardsManager The Rewards Manager contract address
* @param graphTokenGateway The Token Gateway contract address
* @param graphProxyAdmin The Graph Proxy Admin contract address
* @param graphCuration The Curation contract address
*/
event GraphDirectoryInitialized(
address indexed graphToken,
address indexed graphStaking,
address graphPayments,
address graphEscrow,
address indexed graphController,
address graphEpochManager,
address graphRewardsManager,
address graphTokenGateway,
address graphProxyAdmin,
address graphCuration
);
/**
* @notice Thrown when either the controller is the zero address or a contract address is not found
* on the controller
* @param contractName The name of the contract that was not found, or the controller
*/
error GraphDirectoryInvalidZeroAddress(bytes contractName);
/**
* @notice Constructor for the GraphDirectory contract
* @dev Requirements:
* - `controller` cannot be zero address
*
* Emits a {GraphDirectoryInitialized} event
*
* @param controller The address of the Graph Controller contract.
*/
constructor(address controller) {
require(controller != address(0), GraphDirectoryInvalidZeroAddress("Controller"));
GRAPH_CONTROLLER = IController(controller);
GRAPH_TOKEN = IGraphToken(_getContractFromController("GraphToken"));
GRAPH_STAKING = IHorizonStaking(_getContractFromController("Staking"));
GRAPH_PAYMENTS = IGraphPayments(_getContractFromController("GraphPayments"));
GRAPH_PAYMENTS_ESCROW = IPaymentsEscrow(_getContractFromController("PaymentsEscrow"));
GRAPH_EPOCH_MANAGER = IEpochManager(_getContractFromController("EpochManager"));
GRAPH_REWARDS_MANAGER = IRewardsManager(_getContractFromController("RewardsManager"));
GRAPH_TOKEN_GATEWAY = ITokenGateway(_getContractFromController("GraphTokenGateway"));
GRAPH_PROXY_ADMIN = IGraphProxyAdmin(_getContractFromController("GraphProxyAdmin"));
GRAPH_CURATION = ICuration(_getContractFromController("Curation"));
emit GraphDirectoryInitialized(
address(GRAPH_TOKEN),
address(GRAPH_STAKING),
address(GRAPH_PAYMENTS),
address(GRAPH_PAYMENTS_ESCROW),
address(GRAPH_CONTROLLER),
address(GRAPH_EPOCH_MANAGER),
address(GRAPH_REWARDS_MANAGER),
address(GRAPH_TOKEN_GATEWAY),
address(GRAPH_PROXY_ADMIN),
address(GRAPH_CURATION)
);
}
/**
* @notice Get the Graph Token contract
* @return The Graph Token contract
*/
function _graphToken() internal view returns (IGraphToken) {
return GRAPH_TOKEN;
}
/**
* @notice Get the Horizon Staking contract
* @return The Horizon Staking contract
*/
function _graphStaking() internal view returns (IHorizonStaking) {
return GRAPH_STAKING;
}
/**
* @notice Get the Graph Payments contract
* @return The Graph Payments contract
*/
function _graphPayments() internal view returns (IGraphPayments) {
return GRAPH_PAYMENTS;
}
/**
* @notice Get the Payments Escrow contract
* @return The Payments Escrow contract
*/
function _graphPaymentsEscrow() internal view returns (IPaymentsEscrow) {
return GRAPH_PAYMENTS_ESCROW;
}
/**
* @notice Get the Graph Controller contract
* @return The Graph Controller contract
*/
function _graphController() internal view returns (IController) {
return GRAPH_CONTROLLER;
}
/**
* @notice Get the Epoch Manager contract
* @return The Epoch Manager contract
*/
function _graphEpochManager() internal view returns (IEpochManager) {
return GRAPH_EPOCH_MANAGER;
}
/**
* @notice Get the Rewards Manager contract
* @return The Rewards Manager contract address
*/
function _graphRewardsManager() internal view returns (IRewardsManager) {
return GRAPH_REWARDS_MANAGER;
}
/**
* @notice Get the Graph Token Gateway contract
* @return The Graph Token Gateway contract
*/
function _graphTokenGateway() internal view returns (ITokenGateway) {
return GRAPH_TOKEN_GATEWAY;
}
/**
* @notice Get the Graph Proxy Admin contract
* @return The Graph Proxy Admin contract
*/
function _graphProxyAdmin() internal view returns (IGraphProxyAdmin) {
return GRAPH_PROXY_ADMIN;
}
/**
* @notice Get the Curation contract
* @return The Curation contract
*/
function _graphCuration() internal view returns (ICuration) {
return GRAPH_CURATION;
}
/**
* @notice Get a contract address from the controller
* @dev Requirements:
* - The `_contractName` must be registered in the controller
* @param _contractName The name of the contract to fetch from the controller
* @return The address of the contract
*/
function _getContractFromController(bytes memory _contractName) private view returns (address) {
address contractAddress = GRAPH_CONTROLLER.getContractProxy(keccak256(_contractName));
require(contractAddress != address(0), GraphDirectoryInvalidZeroAddress(_contractName));
return contractAddress;
}
}{
"optimizer": {
"enabled": true,
"runs": 20
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_logic","type":"address"},{"internalType":"address","name":"initialOwner","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"ERC1967InvalidAdmin","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedCall","type":"error"},{"inputs":[],"name":"ProxyDeniedAdminAccess","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"}]Contract Creation Code
60a0604052604051610eac380380610eac8339810160408190526100229161039c565b828161002e828261008f565b50508160405161003d90610339565b6001600160a01b039091168152602001604051809103906000f080158015610069573d6000803e3d6000fd5b506001600160a01b031660805261008761008260805190565b6100ee565b50505061048e565b6100988261015c565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a28051156100e2576100dd82826101db565b505050565b6100ea610252565b5050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61012e600080516020610e8c833981519152546001600160a01b031690565b604080516001600160a01b03928316815291841660208301520160405180910390a161015981610273565b50565b806001600160a01b03163b60000361019757604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b6060600080846001600160a01b0316846040516101f89190610472565b600060405180830381855af49150503d8060008114610233576040519150601f19603f3d011682016040523d82523d6000602084013e610238565b606091505b5090925090506102498583836102b2565b95945050505050565b34156102715760405163b398979f60e01b815260040160405180910390fd5b565b6001600160a01b03811661029d57604051633173bdd160e11b81526000600482015260240161018e565b80600080516020610e8c8339815191526101ba565b6060826102c7576102c282610311565b61030a565b81511580156102de57506001600160a01b0384163b155b1561030757604051639996b31560e01b81526001600160a01b038516600482015260240161018e565b50805b9392505050565b80511561032057805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b6105538061093983390190565b80516001600160a01b038116811461035d57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561039357818101518382015260200161037b565b50506000910152565b6000806000606084860312156103b157600080fd5b6103ba84610346565b92506103c860208501610346565b60408501519092506001600160401b038111156103e457600080fd5b8401601f810186136103f557600080fd5b80516001600160401b0381111561040e5761040e610362565b604051601f8201601f19908116603f011681016001600160401b038111828210171561043c5761043c610362565b60405281815282820160200188101561045457600080fd5b610465826020830160208601610378565b8093505050509250925092565b60008251610484818460208701610378565b9190910192915050565b6080516104916104a86000396000601001526104916000f3fe608060405261000c61000e565b005b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316330361007b576000356001600160e01b03191663278f794360e11b14610071576040516334ad5dbb60e21b815260040160405180910390fd5b610079610083565b565b6100796100b2565b6000806100933660048184610303565b8101906100a09190610343565b915091506100ae82826100c2565b5050565b6100796100bd61011d565b610155565b6100cb82610179565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a28051156101155761011082826101f0565b505050565b6100ae610266565b60006101507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e808015610174573d6000f35b3d6000fd5b806001600160a01b03163b6000036101af5780604051634c9c8ce360e01b81526004016101a69190610418565b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b03168460405161020d919061042c565b600060405180830381855af49150503d8060008114610248576040519150601f19603f3d011682016040523d82523d6000602084013e61024d565b606091505b509150915061025d858383610285565b95945050505050565b34156100795760405163b398979f60e01b815260040160405180910390fd5b60608261029a57610295826102db565b6102d4565b81511580156102b157506001600160a01b0384163b155b156102d15783604051639996b31560e01b81526004016101a69190610418565b50805b9392505050565b8051156102ea57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b6000808585111561031357600080fd5b8386111561032057600080fd5b5050820193919092039150565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561035657600080fd5b82356001600160a01b038116811461036d57600080fd5b915060208301356001600160401b0381111561038857600080fd5b8301601f8101851361039957600080fd5b80356001600160401b038111156103b2576103b261032d565b604051601f8201601f19908116603f011681016001600160401b03811182821017156103e0576103e061032d565b6040528181528282016020018710156103f857600080fd5b816020840160208301376000602083830101528093505050509250929050565b6001600160a01b0391909116815260200190565b6000825160005b8181101561044d5760208186018101518583015201610433565b50600092019182525091905056fea2646970667358221220eaa4d0e123c0e184e480bda270c244713f860409a5ed772fa51aa539917c668b64736f6c634300081b0033608060405234801561001057600080fd5b5060405161055338038061055383398101604081905261002f916100be565b806001600160a01b03811661005e57604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b6100678161006e565b50506100ee565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000602082840312156100d057600080fd5b81516001600160a01b03811681146100e757600080fd5b9392505050565b610456806100fd6000396000f3fe60806040526004361061004a5760003560e01c8063715018a61461004f5780638da5cb5b146100665780639623609d14610091578063ad3cb1cc146100a4578063f2fde38b146100e2575b600080fd5b34801561005b57600080fd5b50610064610102565b005b34801561007257600080fd5b5061007b610116565b604051610088919061025d565b60405180910390f35b61006461009f36600461029c565b610125565b3480156100b057600080fd5b506100d5604051806040016040528060058152602001640352e302e360dc1b81525081565b60405161008891906103bd565b3480156100ee57600080fd5b506100646100fd3660046103d7565b610194565b61010a6101db565b610114600061020d565b565b6000546001600160a01b031690565b61012d6101db565b60405163278f794360e11b81526001600160a01b03841690634f1ef28690349061015d90869086906004016103f4565b6000604051808303818588803b15801561017657600080fd5b505af115801561018a573d6000803e3d6000fd5b5050505050505050565b61019c6101db565b6001600160a01b0381166101cf576000604051631e4fbdf760e01b81526004016101c6919061025d565b60405180910390fd5b6101d88161020d565b50565b336101e4610116565b6001600160a01b031614610114573360405163118cdaa760e01b81526004016101c6919061025d565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0391909116815260200190565b6001600160a01b03811681146101d857600080fd5b634e487b7160e01b600052604160045260246000fd5b6000806000606084860312156102b157600080fd5b83356102bc81610271565b925060208401356102cc81610271565b915060408401356001600160401b038111156102e757600080fd5b8401601f810186136102f857600080fd5b80356001600160401b0381111561031157610311610286565b604051601f8201601f19908116603f011681016001600160401b038111828210171561033f5761033f610286565b60405281815282820160200188101561035757600080fd5b816020840160208301376000602083830101528093505050509250925092565b6000815180845260005b8181101561039d57602081850181015186830182015201610381565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006103d06020830184610377565b9392505050565b6000602082840312156103e957600080fd5b81356103d081610271565b6001600160a01b038316815260406020820181905260009061041890830184610377565b94935050505056fea26469706673582212209d57187b2f2e676b3fd9255a815d2f399e1fd90cbbeb2b135e991955bb74b83a64736f6c634300081b0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610300000000000000000000000001e8a7a9bb397a920d2f565697ace5c2f25d1ada0000000000000000000000004528fd7868c91ef64b9907450ee8d82dc639612c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000
Deployed Bytecode
0x608060405261000c61000e565b005b7f00000000000000000000000049021ada0806cabce24b73002eb4477fe49342156001600160a01b0316330361007b576000356001600160e01b03191663278f794360e11b14610071576040516334ad5dbb60e21b815260040160405180910390fd5b610079610083565b565b6100796100b2565b6000806100933660048184610303565b8101906100a09190610343565b915091506100ae82826100c2565b5050565b6100796100bd61011d565b610155565b6100cb82610179565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a28051156101155761011082826101f0565b505050565b6100ae610266565b60006101507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e808015610174573d6000f35b3d6000fd5b806001600160a01b03163b6000036101af5780604051634c9c8ce360e01b81526004016101a69190610418565b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080846001600160a01b03168460405161020d919061042c565b600060405180830381855af49150503d8060008114610248576040519150601f19603f3d011682016040523d82523d6000602084013e61024d565b606091505b509150915061025d858383610285565b95945050505050565b34156100795760405163b398979f60e01b815260040160405180910390fd5b60608261029a57610295826102db565b6102d4565b81511580156102b157506001600160a01b0384163b155b156102d15783604051639996b31560e01b81526004016101a69190610418565b50805b9392505050565b8051156102ea57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b6000808585111561031357600080fd5b8386111561032057600080fd5b5050820193919092039150565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561035657600080fd5b82356001600160a01b038116811461036d57600080fd5b915060208301356001600160401b0381111561038857600080fd5b8301601f8101851361039957600080fd5b80356001600160401b038111156103b2576103b261032d565b604051601f8201601f19908116603f011681016001600160401b03811182821017156103e0576103e061032d565b6040528181528282016020018710156103f857600080fd5b816020840160208301376000602083830101528093505050509250929050565b6001600160a01b0391909116815260200190565b6000825160005b8181101561044d5760208186018101518583015201610433565b50600092019182525091905056fea2646970667358221220eaa4d0e123c0e184e480bda270c244713f860409a5ed772fa51aa539917c668b64736f6c634300081b0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000001e8a7a9bb397a920d2f565697ace5c2f25d1ada0000000000000000000000004528fd7868c91ef64b9907450ee8d82dc639612c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : _logic (address): 0x01E8A7a9bB397a920D2F565697ACE5c2F25D1aDA
Arg [1] : initialOwner (address): 0x4528FD7868c91Ef64B9907450Ee8d82dC639612c
Arg [2] : _data (bytes): 0x
-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 00000000000000000000000001e8a7a9bb397a920d2f565697ace5c2f25d1ada
Arg [1] : 0000000000000000000000004528fd7868c91ef64b9907450ee8d82dc639612c
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000060
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000000
Net Worth in USD
Net Worth in ETH
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.