Created
February 23, 2021 04:07
-
-
Save pi0neerpat/3a1dcc71b5761f06874a3bd4b73078fe to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.7.3+commit.9bfce1f6.js&optimize=false&runs=200&gist=
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner() | |
async function deployAndLog(name, args = []) { | |
const rawData = await remix.call('fileManager', 'getFile', `browser/artifacts/${name}.json`) | |
const metadata = JSON.parse(rawData) | |
const bytecode = metadata.data.bytecode.object | |
console.log(`Deploying ${name}, bytecode length: ${bytecode.length}, isHexString: ${ethers.utils.isHexString("0x"+bytecode)}`) | |
// const factory = new ethers.ContractFactory(metadata.abi, `0x${metadata.data.bytecode.object}`, signer) | |
const factory = new ethers.ContractFactory(metadata.abi, bytecode, signer) | |
const newContract = await factory.deploy(...args); | |
console.log(`Contract deployed: ${name}.sol at ${newContract.address}`) | |
return newContract.address | |
} | |
async function deploy() { | |
try { | |
const graphProxyAdmin = await deployAndLog("GraphProxyAdmin") | |
const staking = await deployAndLog("Staking") | |
const graphProxy = await deployAndLog("GraphProxy", [staking, graphProxyAdmin]) | |
} catch (e) { | |
console.log(e.message.substring(0,200)) | |
} | |
} | |
deploy(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.7.3; | |
import "./GraphProxy.sol"; | |
import "./StakingTest.sol"; | |
import "./StakingTestV2.sol"; | |
import "./GraphProxyAdmin.sol"; | |
import "./GraphUpgradeable.sol"; | |
contract Deployer { | |
event NewContract(string name, address _contract); | |
address public logicA; | |
address public logicB; | |
address public proxyAdmin; | |
address public proxy; | |
function step1_deploy_all() public { | |
proxyAdmin = address(new GraphProxyAdmin()); | |
emit NewContract("proxyAdmin", proxyAdmin); | |
logicA = address(new StakingTest()); | |
emit NewContract("logicA", logicA); | |
proxy = address(new GraphProxy(logicA, proxyAdmin)); | |
emit NewContract("proxy", proxy); | |
} | |
function step2_upgrade() public { | |
logicB = address(new StakingTestV2()); | |
emit NewContract("logicB", logicB); | |
GraphProxyAdmin(proxyAdmin).upgrade(IGraphProxy(proxy), logicB); | |
GraphProxyAdmin(proxyAdmin).acceptProxy(GraphUpgradeable(logicB), IGraphProxy(proxy)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/utils/Address.sol"; | |
import "./GraphProxyStorage.sol"; | |
/** | |
* @title Graph Proxy | |
* @dev Graph Proxy contract used to delegate call implementation contracts and support upgrades. | |
* This contract should NOT define storage as it is managed by GraphProxyStorage. | |
* This contract implements a proxy that is upgradeable by an admin. | |
* https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#transparent-proxies-and-function-clashes | |
*/ | |
contract GraphProxy is GraphProxyStorage { | |
/** | |
* @dev Modifier used internally that will delegate the call to the implementation unless | |
* the sender is the admin. | |
*/ | |
modifier ifAdmin() { | |
if (msg.sender == _admin()) { | |
_; | |
} else { | |
_fallback(); | |
} | |
} | |
/** | |
* @dev Modifier used internally that will delegate the call to the implementation unless | |
* the sender is the admin or pending implementation. | |
*/ | |
modifier ifAdminOrPendingImpl() { | |
if (msg.sender == _admin() || msg.sender == _pendingImplementation()) { | |
_; | |
} else { | |
_fallback(); | |
} | |
} | |
/** | |
* @dev Contract constructor. | |
* @param _impl Address of the initial implementation | |
* @param _admin Address of the proxy admin | |
*/ | |
constructor(address _impl, address _admin) { | |
assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); | |
assert( | |
IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) | |
); | |
assert( | |
PENDING_IMPLEMENTATION_SLOT == | |
bytes32(uint256(keccak256("eip1967.proxy.pendingImplementation")) - 1) | |
); | |
_setAdmin(_admin); | |
_setPendingImplementation(_impl); | |
} | |
/** | |
* @dev Returns the current admin. | |
* | |
* NOTE: Only the admin and implementation can call this function. | |
* | |
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the | |
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. | |
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` | |
*/ | |
function admin() external ifAdminOrPendingImpl returns (address) { | |
return _admin(); | |
} | |
/** | |
* @dev Returns the current implementation. | |
* | |
* NOTE: Only the admin can call this function. | |
* | |
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the | |
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. | |
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` | |
*/ | |
function implementation() external ifAdminOrPendingImpl returns (address) { | |
return _implementation(); | |
} | |
/** | |
* @dev Returns the current pending implementation. | |
* | |
* NOTE: Only the admin can call this function. | |
* | |
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the | |
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. | |
* `0x9e5eddc59e0b171f57125ab86bee043d9128098c3a6b9adb4f2e86333c2f6f8c` | |
*/ | |
function pendingImplementation() external ifAdminOrPendingImpl returns (address) { | |
return _pendingImplementation(); | |
} | |
/** | |
* @dev Changes the admin of the proxy. | |
* | |
* NOTE: Only the admin can call this function. | |
*/ | |
function setAdmin(address _newAdmin) external ifAdmin { | |
require(_newAdmin != address(0), "Cannot change the admin of a proxy to the zero address"); | |
_setAdmin(_newAdmin); | |
} | |
/** | |
* @dev Upgrades to a new implementation contract. | |
* @param _newImplementation Address of implementation contract | |
* | |
* NOTE: Only the admin can call this function. | |
*/ | |
function upgradeTo(address _newImplementation) external ifAdmin { | |
_setPendingImplementation(_newImplementation); | |
} | |
/** | |
* @dev Admin function for new implementation to accept its role as implementation. | |
*/ | |
function acceptUpgrade() external ifAdminOrPendingImpl { | |
_acceptUpgrade(); | |
} | |
/** | |
* @dev Admin function for new implementation to accept its role as implementation. | |
*/ | |
function acceptUpgradeAndCall(bytes calldata data) external ifAdminOrPendingImpl { | |
_acceptUpgrade(); | |
// solhint-disable-next-line avoid-low-level-calls | |
(bool success, ) = _implementation().delegatecall(data); | |
require(success); | |
} | |
/** | |
* @dev Admin function for new implementation to accept its role as implementation. | |
*/ | |
function _acceptUpgrade() internal { | |
address _pendingImplementation = _pendingImplementation(); | |
require(Address.isContract(_pendingImplementation), "Implementation must be a contract"); | |
require( | |
_pendingImplementation != address(0) && msg.sender == _pendingImplementation, | |
"Caller must be the pending implementation" | |
); | |
_setImplementation(_pendingImplementation); | |
_setPendingImplementation(address(0)); | |
} | |
/** | |
* @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 _fallback() internal { | |
require(msg.sender != _admin(), "Cannot fallback to proxy target"); | |
assembly { | |
// (a) get free memory pointer | |
let ptr := mload(0x40) | |
// (b) get address of the implementation | |
let impl := and(sload(IMPLEMENTATION_SLOT), 0xffffffffffffffffffffffffffffffffffffffff) | |
// (1) copy incoming call data | |
calldatacopy(ptr, 0, calldatasize()) | |
// (2) forward call to logic contract | |
let result := delegatecall(gas(), impl, 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) | |
} | |
} | |
} | |
/** | |
* @dev Fallback function that delegates calls to implementation. Will run if no other | |
* function in the contract matches the call data. | |
*/ | |
fallback() external payable { | |
_fallback(); | |
} | |
/** | |
* @dev Fallback function that delegates calls to implementation. Will run if call data | |
* is empty. | |
*/ | |
receive() external payable { | |
_fallback(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/governance/Governed.sol"; | |
import "./IGraphProxy.sol"; | |
import "./GraphUpgradeable.sol"; | |
/** | |
* @title GraphProxyAdmin | |
* @dev This is the owner of upgradeable proxy contracts. | |
* Proxy contracts use a TransparentProxy pattern, any admin related call | |
* like upgrading a contract or changing the admin needs to be send through | |
* this contract. | |
*/ | |
contract GraphProxyAdmin is Governed { | |
/** | |
* @dev Contract constructor. | |
*/ | |
constructor() { | |
Governed._initialize(msg.sender); | |
} | |
/** | |
* @dev Returns the current implementation of a proxy. | |
* This is needed because only the proxy admin can query it. | |
* @return The address of the current implementation of the proxy. | |
*/ | |
function getProxyImplementation(IGraphProxy _proxy) public view returns (address) { | |
// We need to manually run the static call since the getter cannot be flagged as view | |
// bytes4(keccak256("implementation()")) == 0x5c60da1b | |
(bool success, bytes memory returndata) = address(_proxy).staticcall(hex"5c60da1b"); | |
require(success); | |
return abi.decode(returndata, (address)); | |
} | |
/** | |
* @dev Returns the pending implementation of a proxy. | |
* This is needed because only the proxy admin can query it. | |
* @return The address of the pending implementation of the proxy. | |
*/ | |
function getProxyPendingImplementation(IGraphProxy _proxy) public view returns (address) { | |
// We need to manually run the static call since the getter cannot be flagged as view | |
// bytes4(keccak256("pendingImplementation()")) == 0x396f7b23 | |
(bool success, bytes memory returndata) = address(_proxy).staticcall(hex"396f7b23"); | |
require(success); | |
return abi.decode(returndata, (address)); | |
} | |
/** | |
* @dev Returns the admin of a proxy. Only the admin can query it. | |
* @return The address of the current admin of the proxy. | |
*/ | |
function getProxyAdmin(IGraphProxy _proxy) public view returns (address) { | |
// We need to manually run the static call since the getter cannot be flagged as view | |
// bytes4(keccak256("admin()")) == 0xf851a440 | |
(bool success, bytes memory returndata) = address(_proxy).staticcall(hex"f851a440"); | |
require(success); | |
return abi.decode(returndata, (address)); | |
} | |
/** | |
* @dev Changes the admin of a proxy. | |
* @param _proxy Proxy to change admin. | |
* @param _newAdmin Address to transfer proxy administration to. | |
*/ | |
function changeProxyAdmin(IGraphProxy _proxy, address _newAdmin) public onlyGovernor { | |
_proxy.setAdmin(_newAdmin); | |
} | |
/** | |
* @dev Upgrades a proxy to the newest implementation of a contract. | |
* @param _proxy Proxy to be upgraded. | |
* @param _implementation the address of the Implementation. | |
*/ | |
function upgrade(IGraphProxy _proxy, address _implementation) public onlyGovernor { | |
_proxy.upgradeTo(_implementation); | |
} | |
/** | |
* @dev Accepts a proxy. | |
* @param _implementation Address of the implementation accepting the proxy. | |
* @param _proxy Address of the proxy being accepted. | |
*/ | |
function acceptProxy(GraphUpgradeable _implementation, IGraphProxy _proxy) public onlyGovernor { | |
_implementation.acceptProxy(_proxy); | |
} | |
/** | |
* @dev Accepts a proxy and call a function on the implementation. | |
* @param _implementation Address of the implementation accepting the proxy. | |
* @param _proxy Address of the proxy being accepted. | |
* @param _data Encoded function to call on the implementation after accepting the proxy. | |
*/ | |
function acceptProxyAndCall( | |
GraphUpgradeable _implementation, | |
IGraphProxy _proxy, | |
bytes calldata _data | |
) external onlyGovernor { | |
_implementation.acceptProxyAndCall(_proxy, _data); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
/** | |
* @title Graph Proxy Storage | |
* @dev Contract functions related to getting and setting proxy storage. | |
* This contract does not actually define state variables managed by the compiler | |
* but uses fixed slot locations. | |
*/ | |
contract GraphProxyStorage { | |
/** | |
* @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 Storage slot with the address of the pending implementation. | |
* This is the keccak-256 hash of "eip1967.proxy.pendingImplementation" subtracted by 1, and is | |
* validated in the constructor. | |
*/ | |
bytes32 | |
internal constant PENDING_IMPLEMENTATION_SLOT = 0x9e5eddc59e0b171f57125ab86bee043d9128098c3a6b9adb4f2e86333c2f6f8c; | |
/** | |
* @dev Storage slot with the admin of the contract. | |
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is | |
* validated in the constructor. | |
*/ | |
bytes32 | |
internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; | |
/** | |
* @dev Emitted when pendingImplementation is changed. | |
*/ | |
event PendingImplementationUpdated( | |
address indexed oldPendingImplementation, | |
address indexed newPendingImplementation | |
); | |
/** | |
* @dev Emitted when pendingImplementation is accepted, | |
* which means contract implementation is updated. | |
*/ | |
event ImplementationUpdated( | |
address indexed oldImplementation, | |
address indexed newImplementation | |
); | |
/** | |
* @dev Emitted when the admin account has changed. | |
*/ | |
event AdminUpdated(address indexed oldAdmin, address indexed newAdmin); | |
/** | |
* @dev Modifier to check whether the `msg.sender` is the admin. | |
*/ | |
modifier onlyAdmin() { | |
require(msg.sender == _admin(), "Caller must be admin"); | |
_; | |
} | |
/** | |
* @return adm The admin slot. | |
*/ | |
function _admin() internal view returns (address adm) { | |
bytes32 slot = ADMIN_SLOT; | |
assembly { | |
adm := sload(slot) | |
} | |
} | |
/** | |
* @dev Sets the address of the proxy admin. | |
* @param _newAdmin Address of the new proxy admin | |
*/ | |
function _setAdmin(address _newAdmin) internal { | |
bytes32 slot = ADMIN_SLOT; | |
assembly { | |
sstore(slot, _newAdmin) | |
} | |
emit AdminUpdated(_admin(), _newAdmin); | |
} | |
/** | |
* @dev Returns the current implementation. | |
* @return impl Address of the current implementation | |
*/ | |
function _implementation() internal view returns (address impl) { | |
bytes32 slot = IMPLEMENTATION_SLOT; | |
assembly { | |
impl := sload(slot) | |
} | |
} | |
/** | |
* @dev Returns the current pending implementation. | |
* @return impl Address of the current pending implementation | |
*/ | |
function _pendingImplementation() internal view returns (address impl) { | |
bytes32 slot = PENDING_IMPLEMENTATION_SLOT; | |
assembly { | |
impl := sload(slot) | |
} | |
} | |
/** | |
* @dev Sets the implementation address of the proxy. | |
* @param _newImplementation Address of the new implementation | |
*/ | |
function _setImplementation(address _newImplementation) internal { | |
address oldImplementation = _implementation(); | |
bytes32 slot = IMPLEMENTATION_SLOT; | |
assembly { | |
sstore(slot, _newImplementation) | |
} | |
emit ImplementationUpdated(oldImplementation, _newImplementation); | |
} | |
/** | |
* @dev Sets the pending implementation address of the proxy. | |
* @param _newImplementation Address of the new pending implementation | |
*/ | |
function _setPendingImplementation(address _newImplementation) internal { | |
address oldPendingImplementation = _pendingImplementation(); | |
bytes32 slot = PENDING_IMPLEMENTATION_SLOT; | |
assembly { | |
sstore(slot, _newImplementation) | |
} | |
emit PendingImplementationUpdated(oldPendingImplementation, _newImplementation); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
import "./IGraphProxy.sol"; | |
/** | |
* @title Graph Upgradeable | |
* @dev This contract is intended to be inherited from upgradeable contracts. | |
*/ | |
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(), "Caller must be the implementation"); | |
_; | |
} | |
/** | |
* @dev Returns the current implementation. | |
* @return impl Address of the current implementation | |
*/ | |
function _implementation() internal view returns (address impl) { | |
bytes32 slot = IMPLEMENTATION_SLOT; | |
assembly { | |
impl := sload(slot) | |
} | |
} | |
/** | |
* @dev Accept to be an implementation of proxy. | |
*/ | |
function acceptProxy(IGraphProxy _proxy) external onlyProxyAdmin(_proxy) { | |
_proxy.acceptUpgrade(); | |
} | |
/** | |
* @dev 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. | |
*/ | |
function acceptProxyAndCall(IGraphProxy _proxy, bytes calldata _data) | |
external | |
onlyProxyAdmin(_proxy) | |
{ | |
_proxy.acceptUpgradeAndCall(_data); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
pragma experimental ABIEncoderV2; | |
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/cryptography/ECDSA.sol"; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/upgrades/GraphUpgradeable.sol"; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/staking/IStaking.sol"; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/staking/StakingStorage.sol"; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/staking/libs/Rebates.sol"; | |
import "https://github.com/pi0neerpat/contracts/blob/remix-contracts/contracts/staking/libs/Stakes.sol"; | |
/** | |
* @title Staking contract | |
*/ | |
contract Staking is StakingV2Storage, GraphUpgradeable, IStaking { | |
using SafeMath for uint256; | |
using Stakes for Stakes.Indexer; | |
using Rebates for Rebates.Pool; | |
// 100% in parts per million | |
uint32 private constant MAX_PPM = 1000000; | |
// -- Events -- | |
/** | |
* @dev Emitted when `indexer` update the delegation parameters for its delegation pool. | |
*/ | |
event DelegationParametersUpdated( | |
address indexed indexer, | |
uint32 indexingRewardCut, | |
uint32 queryFeeCut, | |
uint32 cooldownBlocks | |
); | |
/** | |
* @dev Emitted when `indexer` stake `tokens` amount. | |
*/ | |
event StakeDeposited(address indexed indexer, uint256 tokens); | |
/** | |
* @dev Emitted when `indexer` unstaked and locked `tokens` amount `until` block. | |
*/ | |
event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); | |
/** | |
* @dev Emitted when `indexer` withdrew `tokens` staked. | |
*/ | |
event StakeWithdrawn(address indexed indexer, uint256 tokens); | |
/** | |
* @dev Emitted when `indexer` was slashed for a total of `tokens` amount. | |
* Tracks `reward` amount of tokens given to `beneficiary`. | |
*/ | |
event StakeSlashed( | |
address indexed indexer, | |
uint256 tokens, | |
uint256 reward, | |
address beneficiary | |
); | |
/** | |
* @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator | |
* gets `shares` for the delegation pool proportionally to the tokens staked. | |
*/ | |
event StakeDelegated( | |
address indexed indexer, | |
address indexed delegator, | |
uint256 tokens, | |
uint256 shares | |
); | |
/** | |
* @dev Emitted when `delegator` undelegated `tokens` from `indexer`. | |
* Tokens get locked for withdrawal after a period of time. | |
*/ | |
event StakeDelegatedLocked( | |
address indexed indexer, | |
address indexed delegator, | |
uint256 tokens, | |
uint256 shares, | |
uint256 until | |
); | |
/** | |
* @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. | |
*/ | |
event StakeDelegatedWithdrawn( | |
address indexed indexer, | |
address indexed delegator, | |
uint256 tokens | |
); | |
/** | |
* @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` | |
* during `epoch`. | |
* `allocationID` indexer derived address used to identify the allocation. | |
* `metadata` additional information related to the allocation. | |
*/ | |
event AllocationCreated( | |
address indexed indexer, | |
bytes32 indexed subgraphDeploymentID, | |
uint256 epoch, | |
uint256 tokens, | |
address indexed allocationID, | |
bytes32 metadata | |
); | |
/** | |
* @dev Emitted when `indexer` collected `tokens` amount in `epoch` for `allocationID`. | |
* These funds are related to `subgraphDeploymentID`. | |
* The `from` value is the sender of the collected funds. | |
*/ | |
event AllocationCollected( | |
address indexed indexer, | |
bytes32 indexed subgraphDeploymentID, | |
uint256 epoch, | |
uint256 tokens, | |
address indexed allocationID, | |
address from, | |
uint256 curationFees, | |
uint256 rebateFees | |
); | |
/** | |
* @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. | |
* An amount of `tokens` get unallocated from `subgraphDeploymentID`. | |
* The `effectiveAllocation` are the tokens allocated from creation to closing. | |
* This event also emits the POI (proof of indexing) submitted by the indexer. | |
* `isDelegator` is true if the sender was one of the indexer's delegators. | |
*/ | |
event AllocationClosed( | |
address indexed indexer, | |
bytes32 indexed subgraphDeploymentID, | |
uint256 epoch, | |
uint256 tokens, | |
address indexed allocationID, | |
uint256 effectiveAllocation, | |
address sender, | |
bytes32 poi, | |
bool isDelegator | |
); | |
/** | |
* @dev Emitted when `indexer` claimed a rebate on `subgraphDeploymentID` during `epoch` | |
* related to the `forEpoch` rebate pool. | |
* The rebate is for `tokens` amount and `unclaimedAllocationsCount` are left for claim | |
* in the rebate pool. `delegationFees` collected and sent to delegation pool. | |
*/ | |
event RebateClaimed( | |
address indexed indexer, | |
bytes32 indexed subgraphDeploymentID, | |
address indexed allocationID, | |
uint256 epoch, | |
uint256 forEpoch, | |
uint256 tokens, | |
uint256 unclaimedAllocationsCount, | |
uint256 delegationFees | |
); | |
/** | |
* @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. | |
*/ | |
event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); | |
/** | |
* @dev Emitted when `caller` set `assetHolder` address as `allowed` to send funds | |
* to staking contract. | |
*/ | |
event AssetHolderUpdate(address indexed caller, address indexed assetHolder, bool allowed); | |
/** | |
* @dev Emitted when `indexer` set `operator` access. | |
*/ | |
event SetOperator(address indexed indexer, address indexed operator, bool allowed); | |
/** | |
* @dev Emitted when `indexer` set an address to receive rewards. | |
*/ | |
event SetRewardsDestination(address indexed indexer, address indexed destination); | |
/** | |
* @dev Check if the caller is the slasher. | |
*/ | |
modifier onlySlasher { | |
require(slashers[msg.sender] == true, "!slasher"); | |
_; | |
} | |
/** | |
* @dev Check if the caller is authorized (indexer or operator) | |
*/ | |
function _isAuth(address _indexer) private view returns (bool) { | |
return msg.sender == _indexer || isOperator(msg.sender, _indexer) == true; | |
} | |
/** | |
* @dev Initialize this contract. | |
*/ | |
function initialize( | |
address _controller, | |
uint256 _minimumIndexerStake, | |
uint32 _thawingPeriod, | |
uint32 _protocolPercentage, | |
uint32 _curationPercentage, | |
uint32 _channelDisputeEpochs, | |
uint32 _maxAllocationEpochs, | |
uint32 _delegationUnbondingPeriod, | |
uint32 _delegationRatio, | |
uint32 _rebateAlphaNumerator, | |
uint32 _rebateAlphaDenominator | |
) external onlyImpl { | |
Managed._initialize(_controller); | |
// Settings | |
_setMinimumIndexerStake(_minimumIndexerStake); | |
_setThawingPeriod(_thawingPeriod); | |
_setProtocolPercentage(_protocolPercentage); | |
_setCurationPercentage(_curationPercentage); | |
_setChannelDisputeEpochs(_channelDisputeEpochs); | |
_setMaxAllocationEpochs(_maxAllocationEpochs); | |
_setDelegationUnbondingPeriod(_delegationUnbondingPeriod); | |
_setDelegationRatio(_delegationRatio); | |
_setDelegationParametersCooldown(0); | |
_setDelegationTaxPercentage(0); | |
_setRebateRatio(_rebateAlphaNumerator, _rebateAlphaDenominator); | |
} | |
/** | |
* @dev Set the minimum indexer stake required to. | |
* @param _minimumIndexerStake Minimum indexer stake | |
*/ | |
function setMinimumIndexerStake(uint256 _minimumIndexerStake) external override onlyGovernor { | |
_setMinimumIndexerStake(_minimumIndexerStake); | |
} | |
/** | |
* @dev Internal: Set the minimum indexer stake required. | |
* @param _minimumIndexerStake Minimum indexer stake | |
*/ | |
function _setMinimumIndexerStake(uint256 _minimumIndexerStake) private { | |
require(_minimumIndexerStake > 0, "!minimumIndexerStake"); | |
minimumIndexerStake = _minimumIndexerStake; | |
emit ParameterUpdated("minimumIndexerStake"); | |
} | |
/** | |
* @dev Set the thawing period for unstaking. | |
* @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking | |
*/ | |
function setThawingPeriod(uint32 _thawingPeriod) external override onlyGovernor { | |
_setThawingPeriod(_thawingPeriod); | |
} | |
/** | |
* @dev Internal: Set the thawing period for unstaking. | |
* @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking | |
*/ | |
function _setThawingPeriod(uint32 _thawingPeriod) private { | |
require(_thawingPeriod > 0, "!thawingPeriod"); | |
thawingPeriod = _thawingPeriod; | |
emit ParameterUpdated("thawingPeriod"); | |
} | |
/** | |
* @dev Set the curation percentage of query fees sent to curators. | |
* @param _percentage Percentage of query fees sent to curators | |
*/ | |
function setCurationPercentage(uint32 _percentage) external override onlyGovernor { | |
_setCurationPercentage(_percentage); | |
} | |
/** | |
* @dev Internal: Set the curation percentage of query fees sent to curators. | |
* @param _percentage Percentage of query fees sent to curators | |
*/ | |
function _setCurationPercentage(uint32 _percentage) private { | |
// Must be within 0% to 100% (inclusive) | |
require(_percentage <= MAX_PPM, ">percentage"); | |
curationPercentage = _percentage; | |
emit ParameterUpdated("curationPercentage"); | |
} | |
/** | |
* @dev Set a protocol percentage to burn when collecting query fees. | |
* @param _percentage Percentage of query fees to burn as protocol fee | |
*/ | |
function setProtocolPercentage(uint32 _percentage) external override onlyGovernor { | |
_setProtocolPercentage(_percentage); | |
} | |
/** | |
* @dev Internal: Set a protocol percentage to burn when collecting query fees. | |
* @param _percentage Percentage of query fees to burn as protocol fee | |
*/ | |
function _setProtocolPercentage(uint32 _percentage) private { | |
// Must be within 0% to 100% (inclusive) | |
require(_percentage <= MAX_PPM, ">percentage"); | |
protocolPercentage = _percentage; | |
emit ParameterUpdated("protocolPercentage"); | |
} | |
/** | |
* @dev Set the period in epochs that need to pass before fees in rebate pool can be claimed. | |
* @param _channelDisputeEpochs Period in epochs | |
*/ | |
function setChannelDisputeEpochs(uint32 _channelDisputeEpochs) external override onlyGovernor { | |
_setChannelDisputeEpochs(_channelDisputeEpochs); | |
} | |
/** | |
* @dev Internal: Set the period in epochs that need to pass before fees in rebate pool can be claimed. | |
* @param _channelDisputeEpochs Period in epochs | |
*/ | |
function _setChannelDisputeEpochs(uint32 _channelDisputeEpochs) private { | |
require(_channelDisputeEpochs > 0, "!channelDisputeEpochs"); | |
channelDisputeEpochs = _channelDisputeEpochs; | |
emit ParameterUpdated("channelDisputeEpochs"); | |
} | |
/** | |
* @dev Set the max time allowed for indexers stake on allocations. | |
* @param _maxAllocationEpochs Allocation duration limit in epochs | |
*/ | |
function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external override onlyGovernor { | |
_setMaxAllocationEpochs(_maxAllocationEpochs); | |
} | |
/** | |
* @dev Internal: Set the max time allowed for indexers stake on allocations. | |
* @param _maxAllocationEpochs Allocation duration limit in epochs | |
*/ | |
function _setMaxAllocationEpochs(uint32 _maxAllocationEpochs) private { | |
maxAllocationEpochs = _maxAllocationEpochs; | |
emit ParameterUpdated("maxAllocationEpochs"); | |
} | |
/** | |
* @dev Set the rebate ratio (fees to allocated stake). | |
* @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function | |
* @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function | |
*/ | |
function setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) | |
external | |
override | |
onlyGovernor | |
{ | |
_setRebateRatio(_alphaNumerator, _alphaDenominator); | |
} | |
/** | |
* @dev Set the rebate ratio (fees to allocated stake). | |
* @param _alphaNumerator Numerator of `alpha` in the cobb-douglas function | |
* @param _alphaDenominator Denominator of `alpha` in the cobb-douglas function | |
*/ | |
function _setRebateRatio(uint32 _alphaNumerator, uint32 _alphaDenominator) private { | |
require(_alphaNumerator > 0 && _alphaDenominator > 0, "!alpha"); | |
alphaNumerator = _alphaNumerator; | |
alphaDenominator = _alphaDenominator; | |
emit ParameterUpdated("rebateRatio"); | |
} | |
/** | |
* @dev Set the delegation ratio. | |
* If set to 10 it means the indexer can use up to 10x the indexer staked amount | |
* from their delegated tokens | |
* @param _delegationRatio Delegation capacity multiplier | |
*/ | |
function setDelegationRatio(uint32 _delegationRatio) external override onlyGovernor { | |
_setDelegationRatio(_delegationRatio); | |
} | |
/** | |
* @dev Internal: Set the delegation ratio. | |
* If set to 10 it means the indexer can use up to 10x the indexer staked amount | |
* from their delegated tokens | |
* @param _delegationRatio Delegation capacity multiplier | |
*/ | |
function _setDelegationRatio(uint32 _delegationRatio) private { | |
delegationRatio = _delegationRatio; | |
emit ParameterUpdated("delegationRatio"); | |
} | |
/** | |
* @dev Set the delegation parameters. | |
* @param _indexingRewardCut Percentage of indexing rewards left for delegators | |
* @param _queryFeeCut Percentage of query fees left for delegators | |
* @param _cooldownBlocks Period that need to pass to update delegation parameters | |
*/ | |
function setDelegationParameters( | |
uint32 _indexingRewardCut, | |
uint32 _queryFeeCut, | |
uint32 _cooldownBlocks | |
) public override { | |
address indexer = msg.sender; | |
// Incentives must be within bounds | |
require(_queryFeeCut <= MAX_PPM, ">queryFeeCut"); | |
require(_indexingRewardCut <= MAX_PPM, ">indexingRewardCut"); | |
// Cooldown period set by indexer cannot be below protocol global setting | |
require(_cooldownBlocks >= delegationParametersCooldown, "<cooldown"); | |
// Verify the cooldown period passed | |
DelegationPool storage pool = delegationPools[indexer]; | |
require( | |
pool.updatedAtBlock == 0 || | |
pool.updatedAtBlock.add(uint256(pool.cooldownBlocks)) <= block.number, | |
"!cooldown" | |
); | |
// Update delegation params | |
pool.indexingRewardCut = _indexingRewardCut; | |
pool.queryFeeCut = _queryFeeCut; | |
pool.cooldownBlocks = _cooldownBlocks; | |
pool.updatedAtBlock = block.number; | |
emit DelegationParametersUpdated( | |
indexer, | |
_indexingRewardCut, | |
_queryFeeCut, | |
_cooldownBlocks | |
); | |
} | |
/** | |
* @dev Set the time in blocks an indexer needs to wait to change delegation parameters. | |
* @param _blocks Number of blocks to set the delegation parameters cooldown period | |
*/ | |
function setDelegationParametersCooldown(uint32 _blocks) external override onlyGovernor { | |
_setDelegationParametersCooldown(_blocks); | |
} | |
/** | |
* @dev Internal: Set the time in blocks an indexer needs to wait to change delegation parameters. | |
* @param _blocks Number of blocks to set the delegation parameters cooldown period | |
*/ | |
function _setDelegationParametersCooldown(uint32 _blocks) private { | |
delegationParametersCooldown = _blocks; | |
emit ParameterUpdated("delegationParametersCooldown"); | |
} | |
/** | |
* @dev Set the period for undelegation of stake from indexer. | |
* @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating | |
*/ | |
function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) | |
external | |
override | |
onlyGovernor | |
{ | |
_setDelegationUnbondingPeriod(_delegationUnbondingPeriod); | |
} | |
/** | |
* @dev Internal: Set the period for undelegation of stake from indexer. | |
* @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating | |
*/ | |
function _setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) private { | |
require(_delegationUnbondingPeriod > 0, "!delegationUnbondingPeriod"); | |
delegationUnbondingPeriod = _delegationUnbondingPeriod; | |
emit ParameterUpdated("delegationUnbondingPeriod"); | |
} | |
/** | |
* @dev Set a delegation tax percentage to burn when delegated funds are deposited. | |
* @param _percentage Percentage of delegated tokens to burn as delegation tax | |
*/ | |
function setDelegationTaxPercentage(uint32 _percentage) external override onlyGovernor { | |
_setDelegationTaxPercentage(_percentage); | |
} | |
/** | |
* @dev Internal: Set a delegation tax percentage to burn when delegated funds are deposited. | |
* @param _percentage Percentage of delegated tokens to burn as delegation tax | |
*/ | |
function _setDelegationTaxPercentage(uint32 _percentage) private { | |
// Must be within 0% to 100% (inclusive) | |
require(_percentage <= MAX_PPM, ">percentage"); | |
delegationTaxPercentage = _percentage; | |
emit ParameterUpdated("delegationTaxPercentage"); | |
} | |
/** | |
* @dev Set or unset an address as allowed slasher. | |
* @param _slasher Address of the party allowed to slash indexers | |
* @param _allowed True if slasher is allowed | |
*/ | |
function setSlasher(address _slasher, bool _allowed) external override onlyGovernor { | |
require(_slasher != address(0), "!slasher"); | |
slashers[_slasher] = _allowed; | |
emit SlasherUpdate(msg.sender, _slasher, _allowed); | |
} | |
/** | |
* @dev Set an address as allowed asset holder. | |
* @param _assetHolder Address of allowed source for state channel funds | |
* @param _allowed True if asset holder is allowed | |
*/ | |
function setAssetHolder(address _assetHolder, bool _allowed) external override onlyGovernor { | |
require(_assetHolder != address(0), "!assetHolder"); | |
assetHolders[_assetHolder] = _allowed; | |
emit AssetHolderUpdate(msg.sender, _assetHolder, _allowed); | |
} | |
/** | |
* @dev 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 override returns (bool) { | |
return _getAllocationState(_allocationID) != AllocationState.Null; | |
} | |
/** | |
* @dev 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 override returns (bool) { | |
return stakes[_indexer].hasTokens(); | |
} | |
/** | |
* @dev Return the allocation by ID. | |
* @param _allocationID Address used as allocation identifier | |
* @return Allocation data | |
*/ | |
function getAllocation(address _allocationID) | |
external | |
view | |
override | |
returns (Allocation memory) | |
{ | |
return allocations[_allocationID]; | |
} | |
/** | |
* @dev Return the current state of an allocation. | |
* @param _allocationID Address used as the allocation identifier | |
* @return AllocationState | |
*/ | |
function getAllocationState(address _allocationID) | |
external | |
view | |
override | |
returns (AllocationState) | |
{ | |
return _getAllocationState(_allocationID); | |
} | |
/** | |
* @dev Return the total amount of tokens allocated to subgraph. | |
* @param _subgraphDeploymentID Address used as the allocation identifier | |
* @return Total tokens allocated to subgraph | |
*/ | |
function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) | |
external | |
view | |
override | |
returns (uint256) | |
{ | |
return subgraphAllocations[_subgraphDeploymentID]; | |
} | |
/** | |
* @dev Return the delegation from a delegator to an indexer. | |
* @param _indexer Address of the indexer where funds have been delegated | |
* @param _delegator Address of the delegator | |
* @return Delegation data | |
*/ | |
function getDelegation(address _indexer, address _delegator) | |
external | |
view | |
override | |
returns (Delegation memory) | |
{ | |
return delegationPools[_indexer].delegators[_delegator]; | |
} | |
/** | |
* @dev Return whether the delegator has delegated to the indexer. | |
* @param _indexer Address of the indexer where funds have been delegated | |
* @param _delegator Address of the delegator | |
* @return True if delegator of indexer | |
*/ | |
function isDelegator(address _indexer, address _delegator) public view override returns (bool) { | |
return delegationPools[_indexer].delegators[_delegator].shares > 0; | |
} | |
/** | |
* @dev 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 override returns (uint256) { | |
return stakes[_indexer].tokensStaked; | |
} | |
/** | |
* @dev Get the total amount of tokens available to use in allocations. | |
* This considers the indexer stake and delegated tokens according to delegation ratio | |
* @param _indexer Address of the indexer | |
* @return Amount of tokens staked by the indexer | |
*/ | |
function getIndexerCapacity(address _indexer) public view override returns (uint256) { | |
Stakes.Indexer memory indexerStake = stakes[_indexer]; | |
uint256 tokensDelegated = delegationPools[_indexer].tokens; | |
uint256 tokensDelegatedCap = indexerStake.tokensSecureStake().mul(uint256(delegationRatio)); | |
uint256 tokensDelegatedCapacity = | |
(tokensDelegated < tokensDelegatedCap) ? tokensDelegated : tokensDelegatedCap; | |
return indexerStake.tokensAvailableWithDelegation(tokensDelegatedCapacity); | |
} | |
/** | |
* @dev Returns amount of delegated tokens ready to be withdrawn after unbonding period. | |
* @param _delegation Delegation of tokens from delegator to indexer | |
* @return Amount of tokens to withdraw | |
*/ | |
function getWithdraweableDelegatedTokens(Delegation memory _delegation) | |
public | |
view | |
returns (uint256) | |
{ | |
// There must be locked tokens and period passed | |
uint256 currentEpoch = epochManager().currentEpoch(); | |
if (_delegation.tokensLockedUntil > 0 && currentEpoch >= _delegation.tokensLockedUntil) { | |
return _delegation.tokensLocked; | |
} | |
return 0; | |
} | |
/** | |
* @dev Authorize or unauthorize an address to be an operator. | |
* @param _operator Address to authorize | |
* @param _allowed Whether authorized or not | |
*/ | |
function setOperator(address _operator, bool _allowed) external override { | |
require(_operator != msg.sender, "operator == sender"); | |
operatorAuth[msg.sender][_operator] = _allowed; | |
emit SetOperator(msg.sender, _operator, _allowed); | |
} | |
/** | |
* @dev Return true if operator is allowed for indexer. | |
* @param _operator Address of the operator | |
* @param _indexer Address of the indexer | |
*/ | |
function isOperator(address _operator, address _indexer) public view override returns (bool) { | |
return operatorAuth[_indexer][_operator]; | |
} | |
/** | |
* @dev Deposit tokens on the indexer stake. | |
* @param _tokens Amount of tokens to stake | |
*/ | |
function stake(uint256 _tokens) external override { | |
stakeTo(msg.sender, _tokens); | |
} | |
/** | |
* @dev Deposit tokens on the indexer stake. | |
* @param _indexer Address of the indexer | |
* @param _tokens Amount of tokens to stake | |
*/ | |
function stakeTo(address _indexer, uint256 _tokens) public override notPartialPaused { | |
require(_tokens > 0, "!tokens"); | |
// Ensure minimum stake | |
require( | |
stakes[_indexer].tokensSecureStake().add(_tokens) >= minimumIndexerStake, | |
"!minimumIndexerStake" | |
); | |
// Transfer tokens to stake from caller to this contract | |
require(graphToken().transferFrom(msg.sender, address(this), _tokens), "!transfer"); | |
// Stake the transferred tokens | |
_stake(_indexer, _tokens); | |
} | |
/** | |
* @dev Unstake tokens from the indexer stake, lock them until thawing period expires. | |
* @param _tokens Amount of tokens to unstake | |
*/ | |
function unstake(uint256 _tokens) external override notPartialPaused { | |
address indexer = msg.sender; | |
Stakes.Indexer storage indexerStake = stakes[indexer]; | |
require(_tokens > 0, "!tokens"); | |
require(indexerStake.hasTokens(), "!stake"); | |
require(indexerStake.tokensAvailable() >= _tokens, "!stake-avail"); | |
// Ensure minimum stake | |
uint256 newStake = indexerStake.tokensSecureStake().sub(_tokens); | |
require(newStake == 0 || newStake >= minimumIndexerStake, "!minimumIndexerStake"); | |
// Before locking more tokens, withdraw any unlocked ones | |
uint256 tokensToWithdraw = indexerStake.tokensWithdrawable(); | |
if (tokensToWithdraw > 0) { | |
_withdraw(indexer); | |
} | |
indexerStake.lockTokens(_tokens, thawingPeriod); | |
emit StakeLocked(indexer, indexerStake.tokensLocked, indexerStake.tokensLockedUntil); | |
} | |
/** | |
* @dev Withdraw indexer tokens once the thawing period has passed. | |
*/ | |
function withdraw() external override notPaused { | |
_withdraw(msg.sender); | |
} | |
/** | |
* @dev Set the destination where to send rewards. | |
* @param _destination Rewards destination address. If set to zero, rewards will be restaked | |
*/ | |
function setRewardsDestination(address _destination) external override { | |
rewardsDestination[msg.sender] = _destination; | |
emit SetRewardsDestination(msg.sender, _destination); | |
} | |
/** | |
* @dev Slash the indexer stake. Delegated tokens are not subject to slashing. | |
* 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 slash( | |
address _indexer, | |
uint256 _tokens, | |
uint256 _reward, | |
address _beneficiary | |
) external override onlySlasher notPartialPaused { | |
Stakes.Indexer storage indexerStake = stakes[_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.hasTokens(), "!stake"); | |
require(_tokens <= indexerStake.tokensStaked, "slash>stake"); | |
// Validate beneficiary of slashed tokens | |
require(_beneficiary != address(0), "!beneficiary"); | |
// Slashing more tokens than freely available (over allocation condition) | |
// Unlock locked tokens to avoid the indexer to withdraw them | |
if (_tokens > indexerStake.tokensAvailable() && indexerStake.tokensLocked > 0) { | |
uint256 tokensOverAllocated = _tokens.sub(indexerStake.tokensAvailable()); | |
uint256 tokensToUnlock = | |
(tokensOverAllocated > indexerStake.tokensLocked) | |
? indexerStake.tokensLocked | |
: tokensOverAllocated; | |
indexerStake.unlockTokens(tokensToUnlock); | |
} | |
// Remove tokens to slash from the stake | |
indexerStake.release(_tokens); | |
// -- Interactions -- | |
IGraphToken graphToken = graphToken(); | |
// Set apart the reward for the beneficiary and burn remaining slashed stake | |
_burnTokens(graphToken, _tokens.sub(_reward)); | |
// Give the beneficiary a reward for slashing | |
if (_reward > 0) { | |
require(graphToken.transfer(_beneficiary, _reward), "!transfer"); | |
} | |
emit StakeSlashed(_indexer, _tokens, _reward, _beneficiary); | |
} | |
/** | |
* @dev Delegate tokens to an indexer. | |
* @param _indexer Address of the indexer to delegate tokens to | |
* @param _tokens Amount of tokens to delegate | |
* @return Amount of shares issued of the delegation pool | |
*/ | |
function delegate(address _indexer, uint256 _tokens) | |
external | |
override | |
notPartialPaused | |
returns (uint256) | |
{ | |
address delegator = msg.sender; | |
// Transfer tokens to delegate to this contract | |
require(graphToken().transferFrom(delegator, address(this), _tokens), "!transfer"); | |
// Update state | |
return _delegate(delegator, _indexer, _tokens); | |
} | |
/** | |
* @dev Undelegate tokens from an indexer. | |
* @param _indexer Address of the indexer where tokens had been delegated | |
* @param _shares Amount of shares to return and undelegate tokens | |
* @return Amount of tokens returned for the shares of the delegation pool | |
*/ | |
function undelegate(address _indexer, uint256 _shares) | |
external | |
override | |
notPartialPaused | |
returns (uint256) | |
{ | |
return _undelegate(msg.sender, _indexer, _shares); | |
} | |
/** | |
* @dev Withdraw delegated tokens once the unbonding period has passed. | |
* @param _indexer Withdraw available tokens delegated to indexer | |
* @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address | |
*/ | |
function withdrawDelegated(address _indexer, address _delegateToIndexer) | |
external | |
override | |
notPaused | |
returns (uint256) | |
{ | |
return _withdrawDelegated(msg.sender, _indexer, _delegateToIndexer); | |
} | |
/** | |
* @dev Allocate available tokens to a subgraph deployment. | |
* @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated | |
* @param _tokens Amount of tokens to allocate | |
* @param _allocationID The allocation identifier | |
* @param _metadata IPFS hash for additional information about the allocation | |
* @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` | |
*/ | |
function allocate( | |
bytes32 _subgraphDeploymentID, | |
uint256 _tokens, | |
address _allocationID, | |
bytes32 _metadata, | |
bytes calldata _proof | |
) external override notPaused { | |
_allocate(msg.sender, _subgraphDeploymentID, _tokens, _allocationID, _metadata, _proof); | |
} | |
/** | |
* @dev Allocate available tokens to a subgraph deployment. | |
* @param _indexer Indexer address to allocate funds from. | |
* @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated | |
* @param _tokens Amount of tokens to allocate | |
* @param _allocationID The allocation identifier | |
* @param _metadata IPFS hash for additional information about the allocation | |
* @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` | |
*/ | |
function allocateFrom( | |
address _indexer, | |
bytes32 _subgraphDeploymentID, | |
uint256 _tokens, | |
address _allocationID, | |
bytes32 _metadata, | |
bytes calldata _proof | |
) external override notPaused { | |
_allocate(_indexer, _subgraphDeploymentID, _tokens, _allocationID, _metadata, _proof); | |
} | |
/** | |
* @dev 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 for 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 override notPaused { | |
_closeAllocation(_allocationID, _poi); | |
} | |
/** | |
* @dev Close multiple allocations 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 for rewards set _poi to 0x0 | |
* @param _requests An array of CloseAllocationRequest | |
*/ | |
function closeAllocationMany(CloseAllocationRequest[] calldata _requests) | |
external | |
override | |
notPaused | |
{ | |
for (uint256 i = 0; i < _requests.length; i++) { | |
_closeAllocation(_requests[i].allocationID, _requests[i].poi); | |
} | |
} | |
/** | |
* @dev Close and allocate. This will perform a close and then create a new Allocation | |
* atomically on the same transaction. | |
* @param _closingAllocationID The identifier of the allocation to be closed | |
* @param _poi Proof of indexing submitted for the allocated period | |
* @param _indexer Indexer address to allocate funds from. | |
* @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated | |
* @param _tokens Amount of tokens to allocate | |
* @param _allocationID The allocation identifier | |
* @param _metadata IPFS hash for additional information about the allocation | |
* @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` | |
*/ | |
function closeAndAllocate( | |
address _closingAllocationID, | |
bytes32 _poi, | |
address _indexer, | |
bytes32 _subgraphDeploymentID, | |
uint256 _tokens, | |
address _allocationID, | |
bytes32 _metadata, | |
bytes calldata _proof | |
) external override notPaused { | |
_closeAllocation(_closingAllocationID, _poi); | |
_allocate(_indexer, _subgraphDeploymentID, _tokens, _allocationID, _metadata, _proof); | |
} | |
/** | |
* @dev Collect query fees for an allocation from state channels. | |
* Funds received are only accepted from a valid sender. | |
* @param _tokens Amount of tokens to collect | |
* @param _allocationID Allocation where the tokens will be assigned | |
*/ | |
function collect(uint256 _tokens, address _allocationID) external override { | |
// Allocation identifier validation | |
require(_allocationID != address(0), "!alloc"); | |
// The contract caller must be an authorized asset holder | |
require(assetHolders[msg.sender] == true, "!assetHolder"); | |
// Allocation must exist | |
AllocationState allocState = _getAllocationState(_allocationID); | |
require(allocState != AllocationState.Null, "!collect"); | |
// Get allocation | |
Allocation storage alloc = allocations[_allocationID]; | |
uint256 queryFees = _tokens; | |
uint256 curationFees = 0; | |
bytes32 subgraphDeploymentID = alloc.subgraphDeploymentID; | |
// Process query fees only if non-zero amount | |
if (queryFees > 0) { | |
// Pull tokens to collect from the authorized sender | |
IGraphToken graphToken = graphToken(); | |
require(graphToken.transferFrom(msg.sender, address(this), _tokens), "!transfer"); | |
// -- Collect protocol tax -- | |
// If the Allocation is not active or closed we are going to charge a 100% protocol tax | |
uint256 usedProtocolPercentage = | |
(allocState == AllocationState.Active || allocState == AllocationState.Closed) | |
? protocolPercentage | |
: MAX_PPM; | |
uint256 protocolTax = _collectTax(graphToken, queryFees, usedProtocolPercentage); | |
queryFees = queryFees.sub(protocolTax); | |
// -- Collect curation fees -- | |
// Only if the subgraph deployment is curated | |
curationFees = _collectCurationFees( | |
graphToken, | |
subgraphDeploymentID, | |
queryFees, | |
curationPercentage | |
); | |
queryFees = queryFees.sub(curationFees); | |
// Add funds to the allocation | |
alloc.collectedFees = alloc.collectedFees.add(queryFees); | |
// When allocation is closed redirect funds to the rebate pool | |
// This way we can keep collecting tokens even after the allocation is closed and | |
// before it gets to the finalized state. | |
if (allocState == AllocationState.Closed) { | |
Rebates.Pool storage rebatePool = rebates[alloc.closedAtEpoch]; | |
rebatePool.fees = rebatePool.fees.add(queryFees); | |
} | |
} | |
emit AllocationCollected( | |
alloc.indexer, | |
subgraphDeploymentID, | |
epochManager().currentEpoch(), | |
_tokens, | |
_allocationID, | |
msg.sender, | |
curationFees, | |
queryFees | |
); | |
} | |
/** | |
* @dev Claim tokens from the rebate pool. | |
* @param _allocationID Allocation from where we are claiming tokens | |
* @param _restake True if restake fees instead of transfer to indexer | |
*/ | |
function claim(address _allocationID, bool _restake) external override notPaused { | |
_claim(_allocationID, _restake); | |
} | |
/** | |
* @dev Claim tokens from the rebate pool for many allocations. | |
* @param _allocationID Array of allocations from where we are claiming tokens | |
* @param _restake True if restake fees instead of transfer to indexer | |
*/ | |
function claimMany(address[] calldata _allocationID, bool _restake) | |
external | |
override | |
notPaused | |
{ | |
for (uint256 i = 0; i < _allocationID.length; i++) { | |
_claim(_allocationID[i], _restake); | |
} | |
} | |
/** | |
* @dev Stake tokens on the indexer. | |
* This function does not check minimum indexer stake requirement to allow | |
* to be called by functions that increase the stake when collecting rewards | |
* without reverting | |
* @param _indexer Address of staking party | |
* @param _tokens Amount of tokens to stake | |
*/ | |
function _stake(address _indexer, uint256 _tokens) private { | |
// Deposit tokens into the indexer stake | |
stakes[_indexer].deposit(_tokens); | |
// Initialize the delegation pool the first time | |
if (delegationPools[_indexer].updatedAtBlock == 0) { | |
setDelegationParameters(MAX_PPM, MAX_PPM, delegationParametersCooldown); | |
} | |
emit StakeDeposited(_indexer, _tokens); | |
} | |
/** | |
* @dev Withdraw indexer tokens once the thawing period has passed. | |
* @param _indexer Address of indexer to withdraw funds from | |
*/ | |
function _withdraw(address _indexer) private { | |
// Get tokens available for withdraw and update balance | |
uint256 tokensToWithdraw = stakes[_indexer].withdrawTokens(); | |
require(tokensToWithdraw > 0, "!tokens"); | |
// Return tokens to the indexer | |
require(graphToken().transfer(_indexer, tokensToWithdraw), "!transfer"); | |
emit StakeWithdrawn(_indexer, tokensToWithdraw); | |
} | |
/** | |
* @dev Allocate available tokens to a subgraph deployment. | |
* @param _indexer Indexer address to allocate funds from. | |
* @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated | |
* @param _tokens Amount of tokens to allocate | |
* @param _allocationID The allocationID will work to identify collected funds related to this allocation | |
* @param _metadata Metadata related to the allocation | |
* @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)` | |
*/ | |
function _allocate( | |
address _indexer, | |
bytes32 _subgraphDeploymentID, | |
uint256 _tokens, | |
address _allocationID, | |
bytes32 _metadata, | |
bytes calldata _proof | |
) private { | |
require(_isAuth(_indexer), "!auth"); | |
// Only allocations with a non-zero token amount are allowed | |
require(_tokens > 0, "!tokens"); | |
// Check allocation | |
require(_allocationID != address(0), "!alloc"); | |
require(_getAllocationState(_allocationID) == AllocationState.Null, "!null"); | |
// Caller must prove that they own the private key for the allocationID adddress | |
// The proof is an Ethereum signed message of KECCAK256(indexerAddress,allocationID) | |
bytes32 messageHash = keccak256(abi.encodePacked(_indexer, _allocationID)); | |
bytes32 digest = ECDSA.toEthSignedMessageHash(messageHash); | |
require(ECDSA.recover(digest, _proof) == _allocationID, "!proof"); | |
// Needs to have free capacity not used for other purposes to allocate | |
require(getIndexerCapacity(_indexer) >= _tokens, "!capacity"); | |
// Creates an allocation | |
// Allocation identifiers are not reused | |
// The assetHolder address can send collected funds to the allocation | |
Allocation memory alloc = | |
Allocation( | |
_indexer, | |
_subgraphDeploymentID, | |
_tokens, // Tokens allocated | |
epochManager().currentEpoch(), // createdAtEpoch | |
0, // closedAtEpoch | |
0, // Initialize collected fees | |
0, // Initialize effective allocation | |
_updateRewards(_subgraphDeploymentID) // Initialize accumulated rewards per stake allocated | |
); | |
allocations[_allocationID] = alloc; | |
// Mark allocated tokens as used | |
stakes[_indexer].allocate(alloc.tokens); | |
// Track total allocations per subgraph | |
// Used for rewards calculations | |
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[ | |
alloc.subgraphDeploymentID | |
] | |
.add(alloc.tokens); | |
emit AllocationCreated( | |
_indexer, | |
_subgraphDeploymentID, | |
alloc.createdAtEpoch, | |
alloc.tokens, | |
_allocationID, | |
_metadata | |
); | |
} | |
/** | |
* @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 = allocations[_allocationID]; | |
// Validate that an allocation cannot be closed before one epoch | |
alloc.closedAtEpoch = epochManager().currentEpoch(); | |
uint256 epochs = | |
alloc.createdAtEpoch < alloc.closedAtEpoch | |
? alloc.closedAtEpoch.sub(alloc.createdAtEpoch) | |
: 0; | |
require(epochs > 0, "<epochs"); | |
// Indexer or operator can close an allocation | |
// Delegators are also allowed but only after maxAllocationEpochs passed | |
bool isIndexer = _isAuth(alloc.indexer); | |
if (epochs > maxAllocationEpochs) { | |
require(isIndexer || isDelegator(alloc.indexer, msg.sender), "!auth-or-del"); | |
} else { | |
require(isIndexer, "!auth"); | |
} | |
// Calculate effective allocation for the amount of epochs it remained allocated | |
alloc.effectiveAllocation = _getEffectiveAllocation( | |
maxAllocationEpochs, | |
alloc.tokens, | |
epochs | |
); | |
// Close the allocation and start counting a period to settle remaining payments from | |
// state channels. | |
allocations[_allocationID].closedAtEpoch = alloc.closedAtEpoch; | |
allocations[_allocationID].effectiveAllocation = alloc.effectiveAllocation; | |
// Account collected fees and effective allocation in rebate pool for the epoch | |
Rebates.Pool storage rebatePool = rebates[alloc.closedAtEpoch]; | |
if (!rebatePool.exists()) { | |
rebatePool.init(alphaNumerator, alphaDenominator); | |
} | |
rebatePool.addToPool(alloc.collectedFees, alloc.effectiveAllocation); | |
// Distribute rewards if proof of indexing was presented by the indexer or operator | |
if (isIndexer && _poi != 0) { | |
_distributeRewards(_allocationID, alloc.indexer); | |
} | |
// Free allocated tokens from use | |
stakes[alloc.indexer].unallocate(alloc.tokens); | |
// Track total allocations per subgraph | |
// Used for rewards calculations | |
subgraphAllocations[alloc.subgraphDeploymentID] = subgraphAllocations[ | |
alloc.subgraphDeploymentID | |
] | |
.sub(alloc.tokens); | |
emit AllocationClosed( | |
alloc.indexer, | |
alloc.subgraphDeploymentID, | |
alloc.closedAtEpoch, | |
alloc.tokens, | |
_allocationID, | |
alloc.effectiveAllocation, | |
msg.sender, | |
_poi, | |
!isIndexer | |
); | |
} | |
/** | |
* @dev Claim tokens from the rebate pool. | |
* @param _allocationID Allocation from where we are claiming tokens | |
* @param _restake True if restake fees instead of transfer to indexer | |
*/ | |
function _claim(address _allocationID, bool _restake) private { | |
// Funds can only be claimed after a period of time passed since allocation was closed | |
AllocationState allocState = _getAllocationState(_allocationID); | |
require(allocState == AllocationState.Finalized, "!finalized"); | |
// Get allocation | |
Allocation memory alloc = allocations[_allocationID]; | |
// Only the indexer or operator can decide if to restake | |
bool restake = _isAuth(alloc.indexer) ? _restake : false; | |
// Process rebate reward | |
Rebates.Pool storage rebatePool = rebates[alloc.closedAtEpoch]; | |
uint256 tokensToClaim = rebatePool.redeem(alloc.collectedFees, alloc.effectiveAllocation); | |
// Add delegation rewards to the delegation pool | |
uint256 delegationRewards = _collectDelegationQueryRewards(alloc.indexer, tokensToClaim); | |
tokensToClaim = tokensToClaim.sub(delegationRewards); | |
// Purge allocation data except for: | |
// - indexer: used in disputes and to avoid reusing an allocationID | |
// - subgraphDeploymentID: used in disputes | |
allocations[_allocationID].tokens = 0; // This avoid collect(), close() and claim() to be called | |
allocations[_allocationID].createdAtEpoch = 0; | |
allocations[_allocationID].closedAtEpoch = 0; | |
allocations[_allocationID].collectedFees = 0; | |
allocations[_allocationID].effectiveAllocation = 0; | |
allocations[_allocationID].accRewardsPerAllocatedToken = 0; | |
// -- Interactions -- | |
IGraphToken graphToken = graphToken(); | |
// When all allocations processed then burn unclaimed fees and prune rebate pool | |
if (rebatePool.unclaimedAllocationsCount == 0) { | |
_burnTokens(graphToken, rebatePool.unclaimedFees()); | |
delete rebates[alloc.closedAtEpoch]; | |
} | |
// When there are tokens to claim from the rebate pool, transfer or restake | |
_sendRewards(graphToken, tokensToClaim, alloc.indexer, restake); | |
emit RebateClaimed( | |
alloc.indexer, | |
alloc.subgraphDeploymentID, | |
_allocationID, | |
epochManager().currentEpoch(), | |
alloc.closedAtEpoch, | |
tokensToClaim, | |
rebatePool.unclaimedAllocationsCount, | |
delegationRewards | |
); | |
} | |
/** | |
* @dev Delegate tokens to an indexer. | |
* @param _delegator Address of the delegator | |
* @param _indexer Address of the indexer to delegate tokens to | |
* @param _tokens Amount of tokens to delegate | |
* @return Amount of shares issued of the delegation pool | |
*/ | |
function _delegate( | |
address _delegator, | |
address _indexer, | |
uint256 _tokens | |
) private returns (uint256) { | |
// Only delegate a non-zero amount of tokens | |
require(_tokens > 0, "!tokens"); | |
// Only delegate to non-empty address | |
require(_indexer != address(0), "!indexer"); | |
// Only delegate to staked indexer | |
require(stakes[_indexer].hasTokens(), "!stake"); | |
// Get the delegation pool of the indexer | |
DelegationPool storage pool = delegationPools[_indexer]; | |
Delegation storage delegation = pool.delegators[_delegator]; | |
// Collect delegation tax | |
uint256 delegationTax = _collectTax(graphToken(), _tokens, delegationTaxPercentage); | |
uint256 delegatedTokens = _tokens.sub(delegationTax); | |
// Calculate shares to issue | |
uint256 shares = | |
(pool.tokens == 0) | |
? delegatedTokens | |
: delegatedTokens.mul(pool.shares).div(pool.tokens); | |
// Update the delegation pool | |
pool.tokens = pool.tokens.add(delegatedTokens); | |
pool.shares = pool.shares.add(shares); | |
// Update the delegation | |
delegation.shares = delegation.shares.add(shares); | |
emit StakeDelegated(_indexer, _delegator, delegatedTokens, shares); | |
return shares; | |
} | |
/** | |
* @dev Undelegate tokens from an indexer. | |
* @param _delegator Address of the delegator | |
* @param _indexer Address of the indexer where tokens had been delegated | |
* @param _shares Amount of shares to return and undelegate tokens | |
* @return Amount of tokens returned for the shares of the delegation pool | |
*/ | |
function _undelegate( | |
address _delegator, | |
address _indexer, | |
uint256 _shares | |
) private returns (uint256) { | |
// Can only undelegate a non-zero amount of shares | |
require(_shares > 0, "!shares"); | |
// Get the delegation pool of the indexer | |
DelegationPool storage pool = delegationPools[_indexer]; | |
Delegation storage delegation = pool.delegators[_delegator]; | |
// Delegator need to have enough shares in the pool to undelegate | |
require(delegation.shares >= _shares, "!shares-avail"); | |
// Withdraw tokens if available | |
if (getWithdraweableDelegatedTokens(delegation) > 0) { | |
_withdrawDelegated(_delegator, _indexer, address(0)); | |
} | |
// Calculate tokens to get in exchange for the shares | |
uint256 tokens = _shares.mul(pool.tokens).div(pool.shares); | |
// Update the delegation pool | |
pool.tokens = pool.tokens.sub(tokens); | |
pool.shares = pool.shares.sub(_shares); | |
// Update the delegation | |
delegation.shares = delegation.shares.sub(_shares); | |
delegation.tokensLocked = delegation.tokensLocked.add(tokens); | |
delegation.tokensLockedUntil = epochManager().currentEpoch().add(delegationUnbondingPeriod); | |
emit StakeDelegatedLocked( | |
_indexer, | |
_delegator, | |
tokens, | |
_shares, | |
delegation.tokensLockedUntil | |
); | |
return tokens; | |
} | |
/** | |
* @dev Withdraw delegated tokens once the unbonding period has passed. | |
* @param _delegator Delegator that is withdrawing tokens | |
* @param _indexer Withdraw available tokens delegated to indexer | |
* @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address | |
*/ | |
function _withdrawDelegated( | |
address _delegator, | |
address _indexer, | |
address _delegateToIndexer | |
) private returns (uint256) { | |
// Get the delegation pool of the indexer | |
DelegationPool storage pool = delegationPools[_indexer]; | |
Delegation storage delegation = pool.delegators[_delegator]; | |
// Validation | |
uint256 tokensToWithdraw = getWithdraweableDelegatedTokens(delegation); | |
require(tokensToWithdraw > 0, "!tokens"); | |
// Reset lock | |
delegation.tokensLocked = 0; | |
delegation.tokensLockedUntil = 0; | |
emit StakeDelegatedWithdrawn(_indexer, _delegator, tokensToWithdraw); | |
// -- Interactions -- | |
if (_delegateToIndexer != address(0)) { | |
// Re-delegate tokens to a new indexer | |
_delegate(_delegator, _delegateToIndexer, tokensToWithdraw); | |
} else { | |
// Return tokens to the delegator | |
require(graphToken().transfer(_delegator, tokensToWithdraw), "!transfer"); | |
} | |
return tokensToWithdraw; | |
} | |
/** | |
* @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; | |
DelegationPool storage pool = delegationPools[_indexer]; | |
if (pool.tokens > 0 && pool.queryFeeCut < MAX_PPM) { | |
uint256 indexerCut = uint256(pool.queryFeeCut).mul(_tokens).div(MAX_PPM); | |
delegationRewards = _tokens.sub(indexerCut); | |
pool.tokens = pool.tokens.add(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; | |
DelegationPool storage pool = delegationPools[_indexer]; | |
if (pool.tokens > 0 && pool.indexingRewardCut < MAX_PPM) { | |
uint256 indexerCut = uint256(pool.indexingRewardCut).mul(_tokens).div(MAX_PPM); | |
delegationRewards = _tokens.sub(indexerCut); | |
pool.tokens = pool.tokens.add(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 _graphToken Token to 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 _curationPercentage Percentage of tokens to collect as fees | |
* @return Amount of curation fees | |
*/ | |
function _collectCurationFees( | |
IGraphToken _graphToken, | |
bytes32 _subgraphDeploymentID, | |
uint256 _tokens, | |
uint256 _curationPercentage | |
) private returns (uint256) { | |
if (_tokens == 0) { | |
return 0; | |
} | |
ICuration curation = curation(); | |
bool isCurationEnabled = _curationPercentage > 0 && address(curation) != address(0); | |
if (isCurationEnabled && curation.isCurated(_subgraphDeploymentID)) { | |
uint256 curationFees = uint256(_curationPercentage).mul(_tokens).div(MAX_PPM); | |
if (curationFees > 0) { | |
// Transfer and call collect() | |
// This function transfer tokens to a trusted protocol contracts | |
// Then we call collect() to do the transfer bookeeping | |
require(_graphToken.transfer(address(curation), curationFees), "!transfer"); | |
curation.collect(_subgraphDeploymentID, curationFees); | |
} | |
return curationFees; | |
} | |
return 0; | |
} | |
/** | |
* @dev Collect tax to burn for an amount of tokens. | |
* @param _graphToken Token to burn | |
* @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( | |
IGraphToken _graphToken, | |
uint256 _tokens, | |
uint256 _percentage | |
) private returns (uint256) { | |
uint256 tax = uint256(_percentage).mul(_tokens).div(MAX_PPM); | |
_burnTokens(_graphToken, tax); // Burn tax if any | |
return tax; | |
} | |
/** | |
* @dev Return the current state of an allocation | |
* @param _allocationID Allocation identifier | |
* @return AllocationState | |
*/ | |
function _getAllocationState(address _allocationID) private view returns (AllocationState) { | |
Allocation storage alloc = allocations[_allocationID]; | |
if (alloc.indexer == address(0)) { | |
return AllocationState.Null; | |
} | |
if (alloc.tokens == 0) { | |
return AllocationState.Claimed; | |
} | |
uint256 closedAtEpoch = alloc.closedAtEpoch; | |
if (closedAtEpoch == 0) { | |
return AllocationState.Active; | |
} | |
uint256 epochs = epochManager().epochsSince(closedAtEpoch); | |
if (epochs >= channelDisputeEpochs) { | |
return AllocationState.Finalized; | |
} | |
return AllocationState.Closed; | |
} | |
/** | |
* @dev Get the effective stake allocation considering epochs from allocation to closing. | |
* @param _maxAllocationEpochs Max amount of epochs to cap the allocated stake | |
* @param _tokens Amount of tokens allocated | |
* @param _numEpochs Number of epochs that passed from allocation to closing | |
* @return Effective allocated tokens across epochs | |
*/ | |
function _getEffectiveAllocation( | |
uint256 _maxAllocationEpochs, | |
uint256 _tokens, | |
uint256 _numEpochs | |
) private pure returns (uint256) { | |
bool shouldCap = _maxAllocationEpochs > 0 && _numEpochs > _maxAllocationEpochs; | |
return _tokens.mul((shouldCap) ? _maxAllocationEpochs : _numEpochs); | |
} | |
/** | |
* @dev Triggers an update of rewards due to a change in allocations. | |
* @param _subgraphDeploymentID Subgraph deployment updated | |
*/ | |
function _updateRewards(bytes32 _subgraphDeploymentID) private returns (uint256) { | |
IRewardsManager rewardsManager = rewardsManager(); | |
if (address(rewardsManager) == address(0)) { | |
return 0; | |
} | |
return rewardsManager.onSubgraphAllocationUpdate(_subgraphDeploymentID); | |
} | |
/** | |
* @dev Assign rewards for the closed allocation to indexer and delegators. | |
* @param _allocationID Allocation | |
*/ | |
function _distributeRewards(address _allocationID, address _indexer) private { | |
IRewardsManager rewardsManager = rewardsManager(); | |
if (address(rewardsManager) == address(0)) { | |
return; | |
} | |
// 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 = rewardsManager.takeRewards(_allocationID); | |
if (totalRewards == 0) { | |
return; | |
} | |
// Calculate delegation rewards and add them to the delegation pool | |
uint256 delegationRewards = _collectDelegationIndexingRewards(_indexer, totalRewards); | |
uint256 indexerRewards = totalRewards.sub(delegationRewards); | |
// Send the indexer rewards | |
_sendRewards( | |
graphToken(), | |
indexerRewards, | |
_indexer, | |
rewardsDestination[_indexer] == address(0) | |
); | |
} | |
/** | |
* @dev Send rewards to the appropiate destination. | |
* @param _graphToken Graph token | |
* @param _amount Number of rewards tokens | |
* @param _beneficiary Address of the beneficiary of rewards | |
* @param _restake Whether to restake or not | |
*/ | |
function _sendRewards( | |
IGraphToken _graphToken, | |
uint256 _amount, | |
address _beneficiary, | |
bool _restake | |
) private { | |
if (_amount == 0) return; | |
if (_restake) { | |
// Restake to place fees into the indexer stake | |
_stake(_beneficiary, _amount); | |
} else { | |
// Transfer funds to the beneficiary's designated rewards destination if set | |
address destination = rewardsDestination[_beneficiary]; | |
require( | |
_graphToken.transfer( | |
destination == address(0) ? _beneficiary : destination, | |
_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) private { | |
if (_amount > 0) { | |
_graphToken.burn(_amount); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
pragma experimental ABIEncoderV2; | |
import "./GraphUpgradeable.sol"; | |
contract StakingTest is GraphUpgradeable { | |
event AllocationClosed( | |
address indexed allocationID, | |
address sender, | |
bytes32 poi | |
); | |
function closeAllocation(address _allocationID, bytes32 _poi) external { | |
emit AllocationClosed( | |
_allocationID, | |
msg.sender, | |
_poi | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.7.3; | |
pragma experimental ABIEncoderV2; | |
import "./StakingTest.sol"; | |
contract StakingTestV2 is StakingTest { | |
event SetRewardsDestination(address indexed destination); | |
function setRewardsDestination(address _destination) external { | |
emit SetRewardsDestination(_destination); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment