Skip to content

Instantly share code, notes, and snippets.

@pi0neerpat
Created February 23, 2021 04:07
Show Gist options
  • Save pi0neerpat/3a1dcc71b5761f06874a3bd4b73078fe to your computer and use it in GitHub Desktop.
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=
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();
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));
}
}
// 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();
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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;
}
// 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);
}
}
}
// 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
);
}
}
// 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