diff --git a/packages/contracts/contracts/arbitrum/AddressAliasHelper.sol b/packages/contracts/contracts/arbitrum/AddressAliasHelper.sol index 740b70361..005df41c0 100644 --- a/packages/contracts/contracts/arbitrum/AddressAliasHelper.sol +++ b/packages/contracts/contracts/arbitrum/AddressAliasHelper.sol @@ -25,8 +25,15 @@ pragma solidity ^0.7.6; +/** + * @title Address Alias Helper Library + * @author Edge & Node + * @notice Utility library for converting addresses between L1 and L2 in Arbitrum + */ library AddressAliasHelper { - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + /// @dev Offset used for L1 to L2 address aliasing + // solhint-disable-next-line const-name-snakecase + uint160 internal constant offset = uint160(0x1111000000000000000000000000000000001111); /// @notice Utility function that converts the address in the L1 that submitted a tx to /// the inbox to the msg.sender viewed in the L2 diff --git a/packages/contracts/contracts/arbitrum/IArbToken.sol b/packages/contracts/contracts/arbitrum/IArbToken.sol deleted file mode 100644 index d7d5a2d8c..000000000 --- a/packages/contracts/contracts/arbitrum/IArbToken.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2020, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -/** - * @title Minimum expected interface for L2 token that interacts with the L2 token bridge (this is the interface necessary - * for a custom token that interacts with the bridge, see TestArbCustomToken.sol for an example implementation). - */ -pragma solidity ^0.7.6; - -interface IArbToken { - /** - * @notice should increase token supply by amount, and should (probably) only be callable by the L1 bridge. - */ - function bridgeMint(address account, uint256 amount) external; - - /** - * @notice should decrease token supply by amount, and should (probably) only be callable by the L1 bridge. - */ - function bridgeBurn(address account, uint256 amount) external; - - /** - * @return address of layer 1 token - */ - function l1Address() external view returns (address); -} diff --git a/packages/contracts/contracts/arbitrum/IBridge.sol b/packages/contracts/contracts/arbitrum/IBridge.sol deleted file mode 100644 index 536ee075b..000000000 --- a/packages/contracts/contracts/arbitrum/IBridge.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.6; - -interface IBridge { - event MessageDelivered( - uint256 indexed messageIndex, - bytes32 indexed beforeInboxAcc, - address inbox, - uint8 kind, - address sender, - bytes32 messageDataHash - ); - - event BridgeCallTriggered(address indexed outbox, address indexed destAddr, uint256 amount, bytes data); - - event InboxToggle(address indexed inbox, bool enabled); - - event OutboxToggle(address indexed outbox, bool enabled); - - function deliverMessageToInbox( - uint8 kind, - address sender, - bytes32 messageDataHash - ) external payable returns (uint256); - - function executeCall( - address destAddr, - uint256 amount, - bytes calldata data - ) external returns (bool success, bytes memory returnData); - - // These are only callable by the admin - function setInbox(address inbox, bool enabled) external; - - function setOutbox(address inbox, bool enabled) external; - - // View functions - - function activeOutbox() external view returns (address); - - function allowedInboxes(address inbox) external view returns (bool); - - function allowedOutboxes(address outbox) external view returns (bool); - - function inboxAccs(uint256 index) external view returns (bytes32); - - function messageCount() external view returns (uint256); -} diff --git a/packages/contracts/contracts/arbitrum/IInbox.sol b/packages/contracts/contracts/arbitrum/IInbox.sol deleted file mode 100644 index a9315bbf8..000000000 --- a/packages/contracts/contracts/arbitrum/IInbox.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.6; - -import "./IBridge.sol"; -import "./IMessageProvider.sol"; - -interface IInbox is IMessageProvider { - function sendL2Message(bytes calldata messageData) external returns (uint256); - - function sendUnsignedTransaction( - uint256 maxGas, - uint256 gasPriceBid, - uint256 nonce, - address destAddr, - uint256 amount, - bytes calldata data - ) external returns (uint256); - - function sendContractTransaction( - uint256 maxGas, - uint256 gasPriceBid, - address destAddr, - uint256 amount, - bytes calldata data - ) external returns (uint256); - - function sendL1FundedUnsignedTransaction( - uint256 maxGas, - uint256 gasPriceBid, - uint256 nonce, - address destAddr, - bytes calldata data - ) external payable returns (uint256); - - function sendL1FundedContractTransaction( - uint256 maxGas, - uint256 gasPriceBid, - address destAddr, - bytes calldata data - ) external payable returns (uint256); - - function createRetryableTicket( - address destAddr, - uint256 arbTxCallValue, - uint256 maxSubmissionCost, - address submissionRefundAddress, - address valueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes calldata data - ) external payable returns (uint256); - - function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); - - function bridge() external view returns (IBridge); - - function pauseCreateRetryables() external; - - function unpauseCreateRetryables() external; - - function startRewriteAddress() external; - - function stopRewriteAddress() external; -} diff --git a/packages/contracts/contracts/arbitrum/IMessageProvider.sol b/packages/contracts/contracts/arbitrum/IMessageProvider.sol deleted file mode 100644 index 8fbfdb171..000000000 --- a/packages/contracts/contracts/arbitrum/IMessageProvider.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.6; - -interface IMessageProvider { - event InboxMessageDelivered(uint256 indexed messageNum, bytes data); - - event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); -} diff --git a/packages/contracts/contracts/arbitrum/IOutbox.sol b/packages/contracts/contracts/arbitrum/IOutbox.sol deleted file mode 100644 index 2e4f05bd5..000000000 --- a/packages/contracts/contracts/arbitrum/IOutbox.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2021, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.6; - -interface IOutbox { - event OutboxEntryCreated( - uint256 indexed batchNum, - uint256 outboxEntryIndex, - bytes32 outputRoot, - uint256 numInBatch - ); - event OutBoxTransactionExecuted( - address indexed destAddr, - address indexed l2Sender, - uint256 indexed outboxEntryIndex, - uint256 transactionIndex - ); - - function l2ToL1Sender() external view returns (address); - - function l2ToL1Block() external view returns (uint256); - - function l2ToL1EthBlock() external view returns (uint256); - - function l2ToL1Timestamp() external view returns (uint256); - - function l2ToL1BatchNum() external view returns (uint256); - - function l2ToL1OutputId() external view returns (bytes32); - - function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) external; - - function outboxEntryExists(uint256 batchNum) external view returns (bool); -} diff --git a/packages/contracts/contracts/arbitrum/ITokenGateway.sol b/packages/contracts/contracts/arbitrum/ITokenGateway.sol deleted file mode 100644 index 3b12e578e..000000000 --- a/packages/contracts/contracts/arbitrum/ITokenGateway.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2020, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.6 || 0.8.27; - -interface ITokenGateway { - /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated - // event OutboundTransferInitiated( - // address token, - // address indexed _from, - // address indexed _to, - // uint256 indexed _transferId, - // uint256 _amount, - // bytes _data - // ); - - /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized - // event InboundTransferFinalized( - // address token, - // address indexed _from, - // address indexed _to, - // uint256 indexed _transferId, - // uint256 _amount, - // bytes _data - // ); - - function outboundTransfer( - address token, - address to, - uint256 amunt, - uint256 maxas, - uint256 gasPiceBid, - bytes calldata data - ) external payable returns (bytes memory); - - function finalizeInboundTransfer( - address token, - address from, - address to, - uint256 amount, - bytes calldata data - ) external payable; - - /** - * @notice Calculate the address used when bridging an ERC20 token - * @dev the L1 and L2 address oracles may not always be in sync. - * For example, a custom token may have been registered but not deployed or the contract self destructed. - * @param l1ERC20 address of L1 token - * @return L2 address of a bridged ERC20 token - */ - function calculateL2TokenAddress(address l1ERC20) external view returns (address); -} diff --git a/packages/contracts/contracts/arbitrum/L1ArbitrumMessenger.sol b/packages/contracts/contracts/arbitrum/L1ArbitrumMessenger.sol index 839e1930b..0428e9b87 100644 --- a/packages/contracts/contracts/arbitrum/L1ArbitrumMessenger.sol +++ b/packages/contracts/contracts/arbitrum/L1ArbitrumMessenger.sol @@ -25,20 +25,49 @@ pragma solidity ^0.7.6; -import "./IInbox.sol"; -import "./IOutbox.sol"; +import { IInbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IInbox.sol"; +import { IOutbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IOutbox.sol"; +import { IBridge } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IBridge.sol"; -/// @notice L1 utility contract to assist with L1 <=> L2 interactions -/// @dev this is an abstract contract instead of library so the functions can be easily overridden when testing +/** + * @title L1 Arbitrum Messenger + * @author Edge & Node + * @notice L1 utility contract to assist with L1 <=> L2 interactions + * @dev this is an abstract contract instead of library so the functions can be easily overridden when testing + */ abstract contract L1ArbitrumMessenger { + /** + * @notice Emitted when a transaction is sent to L2 + * @param _from Address sending the transaction + * @param _to Address receiving the transaction on L2 + * @param _seqNum Sequence number of the retryable ticket + * @param _data Transaction data + */ event TxToL2(address indexed _from, address indexed _to, uint256 indexed _seqNum, bytes _data); + /** + * @dev Parameters for L2 gas configuration + * @param _maxSubmissionCost Maximum cost for submitting the transaction + * @param _maxGas Maximum gas for the L2 transaction + * @param _gasPriceBid Gas price bid for the L2 transaction + */ struct L2GasParams { uint256 _maxSubmissionCost; uint256 _maxGas; uint256 _gasPriceBid; } + /** + * @notice Send a transaction to L2 using gas parameters struct + * @param _inbox Address of the inbox contract + * @param _to Destination address on L2 + * @param _user Address that will be credited as the sender + * @param _l1CallValue ETH value to send with the L1 transaction + * @param _l2CallValue ETH value to send with the L2 transaction + * @param _l2GasParams Gas parameters for the L2 transaction + * @param _data Calldata for the L2 transaction + * @return Sequence number of the retryable ticket + */ function sendTxToL2( address _inbox, address _to, @@ -63,6 +92,19 @@ abstract contract L1ArbitrumMessenger { ); } + /** + * @notice Send a transaction to L2 with individual gas parameters + * @param _inbox Address of the inbox contract + * @param _to Destination address on L2 + * @param _user Address that will be credited as the sender + * @param _l1CallValue ETH value to send with the L1 transaction + * @param _l2CallValue ETH value to send with the L2 transaction + * @param _maxSubmissionCost Maximum cost for submitting the transaction + * @param _maxGas Maximum gas for the L2 transaction + * @param _gasPriceBid Gas price bid for the L2 transaction + * @param _data Calldata for the L2 transaction + * @return Sequence number of the retryable ticket + */ function sendTxToL2( address _inbox, address _to, @@ -88,11 +130,21 @@ abstract contract L1ArbitrumMessenger { return seqNum; } + /** + * @notice Get the bridge contract from an inbox + * @param _inbox Address of the inbox contract + * @return Bridge contract interface + */ function getBridge(address _inbox) internal view virtual returns (IBridge) { return IInbox(_inbox).bridge(); } - /// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies + /** + * @notice Get the L2 to L1 sender address from the outbox + * @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies + * @param _inbox Address of the inbox contract + * @return Address of the L2 to L1 sender + */ function getL2ToL1Sender(address _inbox) internal view virtual returns (address) { IOutbox outbox = IOutbox(getBridge(_inbox).activeOutbox()); address l2ToL1Sender = outbox.l2ToL1Sender(); diff --git a/packages/contracts/contracts/arbitrum/L2ArbitrumMessenger.sol b/packages/contracts/contracts/arbitrum/L2ArbitrumMessenger.sol index e34a29262..ac6774748 100644 --- a/packages/contracts/contracts/arbitrum/L2ArbitrumMessenger.sol +++ b/packages/contracts/contracts/arbitrum/L2ArbitrumMessenger.sol @@ -25,15 +25,35 @@ pragma solidity ^0.7.6; -import "arbos-precompiles/arbos/builtin/ArbSys.sol"; +import { ArbSys } from "arbos-precompiles/arbos/builtin/ArbSys.sol"; -/// @notice L2 utility contract to assist with L1 <=> L2 interactions -/// @dev this is an abstract contract instead of library so the functions can be easily overridden when testing +/** + * @title L2 Arbitrum Messenger + * @author Edge & Node + * @notice L2 utility contract to assist with L1 <=> L2 interactions + * @dev this is an abstract contract instead of library so the functions can be easily overridden when testing + */ abstract contract L2ArbitrumMessenger { + /// @dev Address of the ArbSys precompile address internal constant ARB_SYS_ADDRESS = address(100); + /** + * @notice Emitted when a transaction is sent to L1 + * @param _from Address sending the transaction + * @param _to Address receiving the transaction on L1 + * @param _id ID of the L2 to L1 message + * @param _data Transaction data + */ event TxToL1(address indexed _from, address indexed _to, uint256 indexed _id, bytes _data); + /** + * @notice Send a transaction from L2 to L1 + * @param _l1CallValue ETH value to send with the L1 transaction + * @param _from Address that is sending the transaction + * @param _to Destination address on L1 + * @param _data Calldata for the L1 transaction + * @return ID of the L2 to L1 message + */ function sendTxToL1( uint256 _l1CallValue, address _from, diff --git a/packages/contracts/contracts/bancor/BancorFormula.sol b/packages/contracts/contracts/bancor/BancorFormula.sol index 689eebaba..0d221be56 100644 --- a/packages/contracts/contracts/bancor/BancorFormula.sol +++ b/packages/contracts/contracts/bancor/BancorFormula.sol @@ -2,35 +2,55 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts/math/SafeMath.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-increment-by-one, gas-strict-inequalities +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +/** + * @title Bancor Formula Contract + * @author Edge & Node + * @notice Contract implementing Bancor's bonding curve formula for token conversion + */ contract BancorFormula { using SafeMath for uint256; - uint16 public constant version = 6; + /// @notice Version of the Bancor formula implementation + uint16 public constant version = 6; // solhint-disable-line const-name-snakecase + /// @dev Constant representing the value 1 uint256 private constant ONE = 1; + /// @dev Maximum ratio value (100% in parts per million) uint32 private constant MAX_RATIO = 1000000; + /// @dev Minimum precision for calculations uint8 private constant MIN_PRECISION = 32; + /// @dev Maximum precision for calculations uint8 private constant MAX_PRECISION = 127; /** * @dev Auto-generated via 'PrintIntScalingFactors.py' */ + /// @dev Fixed point representation of 1 (2^127) uint256 private constant FIXED_1 = 0x080000000000000000000000000000000; + /// @dev Fixed point representation of 2 (2^128) uint256 private constant FIXED_2 = 0x100000000000000000000000000000000; + /// @dev Maximum number for calculations (2^129) uint256 private constant MAX_NUM = 0x200000000000000000000000000000000; /** * @dev Auto-generated via 'PrintLn2ScalingFactors.py' */ + /// @dev Natural logarithm of 2 numerator for fixed point calculations uint256 private constant LN2_NUMERATOR = 0x3f80fe03f80fe03f80fe03f80fe03f8; + /// @dev Natural logarithm of 2 denominator for fixed point calculations uint256 private constant LN2_DENOMINATOR = 0x5b9de1d10bf4103d647b0955897ba80; /** * @dev Auto-generated via 'PrintFunctionOptimalLog.py' and 'PrintFunctionOptimalExp.py' */ + /// @dev Maximum value for optimal logarithm calculation uint256 private constant OPT_LOG_MAX_VAL = 0x15bf0a8b1457695355fb8ac404e7a79e3; + /// @dev Maximum value for optimal exponentiation calculation uint256 private constant OPT_EXP_MAX_VAL = 0x800000000000000000000000000000000; /** @@ -38,6 +58,7 @@ contract BancorFormula { */ uint256[128] private maxExpArray; + /// @notice Initialize the Bancor formula with maximum exponent array values constructor() { // maxExpArray[ 0] = 0x6bffffffffffffffffffffffffffffffff; // maxExpArray[ 1] = 0x67ffffffffffffffffffffffffffffffff; @@ -170,7 +191,7 @@ contract BancorFormula { } /** - * @dev given a token supply, reserve balance, ratio and a deposit amount (in the reserve token), + * @notice Given a token supply, reserve balance, ratio and a deposit amount (in the reserve token), * calculates the return for a given conversion (in the main token) * * Formula: @@ -210,7 +231,7 @@ contract BancorFormula { } /** - * @dev given a token supply, reserve balance, ratio and a sell amount (in the main token), + * @notice Given a token supply, reserve balance, ratio and a sell amount (in the main token), * calculates the return for a given conversion (in the reserve token) * * Formula: @@ -258,7 +279,7 @@ contract BancorFormula { } /** - * @dev given two reserve balances/ratios and a sell amount (in the first reserve token), + * @notice Given two reserve balances/ratios and a sell amount (in the first reserve token), * calculates the return for a conversion from the first reserve token to the second reserve token (in the second reserve token) * note that prior to version 4, you should use 'calculateCrossConnectorReturn' instead * @@ -304,7 +325,7 @@ contract BancorFormula { } /** - * @dev given a smart token supply, reserve balance, total ratio and an amount of requested smart tokens, + * @notice Given a smart token supply, reserve balance, total ratio and an amount of requested smart tokens, * calculates the amount of reserve tokens required for purchasing the given amount of smart tokens * * Formula: @@ -341,7 +362,7 @@ contract BancorFormula { } /** - * @dev given a smart token supply, reserve balance, total ratio and an amount of smart tokens to liquidate, + * @notice Given a smart token supply, reserve balance, total ratio and an amount of smart tokens to liquidate, * calculates the amount of reserve tokens received for selling the given amount of smart tokens * * Formula: @@ -384,7 +405,7 @@ contract BancorFormula { } /** - * @dev General Description: + * @notice General Description: * Determine a value of precision. * Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision. * Return the result along with the precision used. @@ -400,6 +421,12 @@ contract BancorFormula { * This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations. * This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul". * Since we rely on unsigned-integer arithmetic and "base < 1" ==> "log(base) < 0", this function does not support "_baseN < _baseD". + * @param _baseN Base numerator + * @param _baseD Base denominator + * @param _expN Exponent numerator + * @param _expD Exponent denominator + * @return result The computed power result + * @return precision The precision used in the calculation */ function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal view returns (uint256, uint8) { require(_baseN < MAX_NUM); @@ -422,8 +449,10 @@ contract BancorFormula { } /** - * @dev computes log(x / FIXED_1) * FIXED_1. + * @notice Computes log(x / FIXED_1) * FIXED_1. * This functions assumes that "x >= FIXED_1", because the output would be negative otherwise. + * @param x The input value (must be >= FIXED_1) + * @return The computed logarithm */ function generalLog(uint256 x) internal pure returns (uint256) { uint256 res = 0; @@ -450,7 +479,9 @@ contract BancorFormula { } /** - * @dev computes the largest integer smaller than or equal to the binary logarithm of the input. + * @notice Computes the largest integer smaller than or equal to the binary logarithm of the input. + * @param _n The input value + * @return The floor of the binary logarithm */ function floorLog2(uint256 _n) internal pure returns (uint8) { uint8 res = 0; @@ -475,9 +506,11 @@ contract BancorFormula { } /** - * @dev the global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent: + * @notice The global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent: * - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"] * - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"] + * @param _x The value to find position for + * @return The position in the maxExpArray */ function findPositionInMaxExpArray(uint256 _x) internal view returns (uint8) { uint8 lo = MIN_PRECISION; @@ -497,11 +530,14 @@ contract BancorFormula { } /** - * @dev this function can be auto-generated by the script 'PrintFunctionGeneralExp.py'. + * @notice This function can be auto-generated by the script 'PrintFunctionGeneralExp.py'. * it approximates "e ^ x" via maclaurin summation: "(x^0)/0! + (x^1)/1! + ... + (x^n)/n!". * it returns "e ^ (x / 2 ^ precision) * 2 ^ precision", that is, the result is upshifted for accuracy. * the global "maxExpArray" maps each "precision" to "((maximumExponent + 1) << (MAX_PRECISION - precision)) - 1". * the maximum permitted value for "x" is therefore given by "maxExpArray[precision] >> (MAX_PRECISION - precision)". + * @param _x The exponent value + * @param _precision The precision to use + * @return The computed exponential result */ function generalExp(uint256 _x, uint8 _precision) internal pure returns (uint256) { uint256 xi = _x; @@ -576,7 +612,7 @@ contract BancorFormula { } /** - * @dev computes log(x / FIXED_1) * FIXED_1 + * @notice Computes log(x / FIXED_1) * FIXED_1 * Input range: FIXED_1 <= x <= LOG_EXP_MAX_VAL - 1 * Auto-generated via 'PrintFunctionOptimalLog.py' * Detailed description: @@ -585,6 +621,8 @@ contract BancorFormula { * - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1 * - The natural logarithm of the input is calculated by summing up the intermediate results above * - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859) + * @param x The input value + * @return The computed logarithm */ function optimalLog(uint256 x) internal pure returns (uint256) { uint256 res = 0; @@ -648,7 +686,7 @@ contract BancorFormula { } /** - * @dev computes e ^ (x / FIXED_1) * FIXED_1 + * @notice Computes e ^ (x / FIXED_1) * FIXED_1 * input range: 0 <= x <= OPT_EXP_MAX_VAL - 1 * auto-generated via 'PrintFunctionOptimalExp.py' * Detailed description: @@ -657,6 +695,8 @@ contract BancorFormula { * - The exponentiation of r is calculated via Taylor series for e^x, where x = r * - The exponentiation of the input is calculated by multiplying the intermediate results above * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859 + * @param x The input value + * @return The computed exponential result */ function optimalExp(uint256 x) internal pure returns (uint256) { uint256 res = 0; @@ -724,7 +764,13 @@ contract BancorFormula { } /** - * @dev deprecated, backward compatibility + * @notice Deprecated function for backward compatibility + * @param _fromConnectorBalance input connector balance + * @param _fromConnectorWeight input connector weight + * @param _toConnectorBalance output connector balance + * @param _toConnectorWeight output connector weight + * @param _amount input connector amount + * @return output connector amount */ function calculateCrossConnectorReturn( uint256 _fromConnectorBalance, diff --git a/packages/contracts/contracts/base/IMulticall.sol b/packages/contracts/contracts/base/IMulticall.sol deleted file mode 100644 index 10f7fa469..000000000 --- a/packages/contracts/contracts/base/IMulticall.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; -pragma abicoder v2; - -/** - * @title Multicall interface - * @notice Enables calling multiple methods in a single call to the contract - */ -interface IMulticall { - /** - * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed - * @param data The encoded function data for each of the calls to make to this contract - * @return results The results from each of the calls passed in via data - */ - function multicall(bytes[] calldata data) external returns (bytes[] memory results); -} diff --git a/packages/contracts/contracts/base/Multicall.sol b/packages/contracts/contracts/base/Multicall.sol index 49111840d..808f7695f 100644 --- a/packages/contracts/contracts/base/Multicall.sol +++ b/packages/contracts/contracts/base/Multicall.sol @@ -3,13 +3,17 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "./IMulticall.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one + +import { IMulticall } from "@graphprotocol/interfaces/contracts/contracts/base/IMulticall.sol"; // Inspired by https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/base/Multicall.sol // Note: Removed payable from the multicall /** * @title Multicall + * @author Edge & Node * @notice Enables calling multiple methods in a single call to the contract */ abstract contract Multicall is IMulticall { @@ -17,11 +21,12 @@ abstract contract Multicall is IMulticall { function multicall(bytes[] calldata data) external override returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { - (bool success, bytes memory result) = address(this).delegatecall(data[i]); + (bool success, bytes memory result) = address(this).delegatecall(data[i]); // solhint-disable-line avoid-low-level-calls if (!success) { // Next 5 lines from https://ethereum.stackexchange.com/a/83577 if (result.length < 68) revert(); + // solhint-disable-next-line no-inline-assembly assembly { result := add(result, 0x04) } diff --git a/packages/contracts/contracts/curation/Curation.sol b/packages/contracts/contracts/curation/Curation.sol index 827c230b7..e7aac2cc2 100644 --- a/packages/contracts/contracts/curation/Curation.sol +++ b/packages/contracts/contracts/curation/Curation.sol @@ -3,6 +3,9 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-small-strings, gas-strict-inequalities + import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { ClonesUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol"; @@ -10,15 +13,16 @@ import { ClonesUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/Clo import { BancorFormula } from "../bancor/BancorFormula.sol"; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; import { TokenUtils } from "../utils/TokenUtils.sol"; -import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; import { Managed } from "../governance/Managed.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { CurationV2Storage } from "./CurationStorage.sol"; -import { IGraphCurationToken } from "./IGraphCurationToken.sol"; +import { IGraphCurationToken } from "@graphprotocol/interfaces/contracts/contracts/curation/IGraphCurationToken.sol"; /** * @title Curation contract - * @dev Allows curators to signal on subgraph deployments that might be relevant to indexers by + * @author Edge & Node + * @notice Allows curators to signal on subgraph deployments that might be relevant to indexers by * staking Graph Tokens (GRT). Additionally, curators earn fees from the Query Market related to the * subgraph deployment they curate. * A curators deposit goes to a curation pool along with the deposits of other curators, @@ -40,9 +44,14 @@ contract Curation is CurationV2Storage, GraphUpgradeable { // -- Events -- /** - * @dev Emitted when `curator` deposited `tokens` on `subgraphDeploymentID` as curation signal. + * @notice Emitted when `curator` deposited `tokens` on `subgraphDeploymentID` as curation signal. * The `curator` receives `signal` amount according to the curation pool bonding curve. * An amount of `curationTax` will be collected and burned. + * @param curator Address of the curator + * @param subgraphDeploymentID Subgraph deployment being signaled on + * @param tokens Amount of tokens deposited + * @param signal Amount of signal minted + * @param curationTax Amount of tokens burned as curation tax */ event Signalled( address indexed curator, @@ -53,14 +62,20 @@ contract Curation is CurationV2Storage, GraphUpgradeable { ); /** - * @dev Emitted when `curator` burned `signal` for a `subgraphDeploymentID`. + * @notice Emitted when `curator` burned `signal` for a `subgraphDeploymentID`. * The curator will receive `tokens` according to the value of the bonding curve. + * @param curator Address of the curator + * @param subgraphDeploymentID Subgraph deployment being signaled on + * @param tokens Amount of tokens received + * @param signal Amount of signal burned */ event Burned(address indexed curator, bytes32 indexed subgraphDeploymentID, uint256 tokens, uint256 signal); /** - * @dev Emitted when `tokens` amount were collected for `subgraphDeploymentID` as part of fees + * @notice Emitted when `tokens` amount were collected for `subgraphDeploymentID` as part of fees * distributed by an indexer from query fees received from state channels. + * @param subgraphDeploymentID Subgraph deployment that collected fees + * @param tokens Amount of tokens collected as fees */ event Collected(bytes32 indexed subgraphDeploymentID, uint256 tokens); @@ -94,8 +109,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Set the default reserve ratio percentage for a curation pool. - * @notice Update the default reserve ratio to `_defaultReserveRatio` + * @notice Set the default reserve ratio percentage for a curation pool. * @param _defaultReserveRatio Reserve ratio (in PPM) */ function setDefaultReserveRatio(uint32 _defaultReserveRatio) external override onlyGovernor { @@ -103,8 +117,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Set the minimum deposit amount for curators. - * @notice Update the minimum deposit amount to `_minimumCurationDeposit` + * @notice Set the minimum deposit amount for curators. * @param _minimumCurationDeposit Minimum amount of tokens required deposit */ function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external override onlyGovernor { @@ -207,7 +220,6 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Return an amount of signal to get tokens back. * @notice Burn _signal from the SubgraphDeployment curation pool * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal * @param _signalIn Amount of signal to return @@ -313,7 +325,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Calculate amount of signal that can be bought with tokens in a curation pool. + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. * @param _subgraphDeploymentID Subgraph deployment to mint signal * @param _tokensIn Amount of tokens used to mint signal * @return Amount of signal that can be bought with tokens @@ -367,7 +379,6 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Internal: Set the default reserve ratio percentage for a curation pool. * @notice Update the default reserver ratio to `_defaultReserveRatio` * @param _defaultReserveRatio Reserve ratio (in PPM) */ @@ -381,7 +392,6 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Internal: Set the minimum deposit amount for curators. * @notice Update the minimum deposit amount to `_minimumCurationDeposit` * @param _minimumCurationDeposit Minimum amount of tokens required deposit */ @@ -393,7 +403,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Internal: Set the curation tax percentage (in PPM) to charge when a curator deposits GRT tokens. + * @notice Internal: Set the curation tax percentage (in PPM) to charge when a curator deposits GRT tokens. * @param _percentage Curation tax charged when depositing GRT tokens in PPM */ function _setCurationTaxPercentage(uint32 _percentage) private { @@ -404,7 +414,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Internal: Set the master copy to use as clones for the curation token. + * @notice Internal: Set the master copy to use as clones for the curation token. * @param _curationTokenMaster Address of implementation contract to use for curation tokens */ function _setCurationTokenMaster(address _curationTokenMaster) private { @@ -416,7 +426,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable { } /** - * @dev Triggers an update of rewards due to a change in signal. + * @notice Triggers an update of rewards due to a change in signal. * @param _subgraphDeploymentID Subgraph deployment updated */ function _updateRewards(bytes32 _subgraphDeploymentID) private { diff --git a/packages/contracts/contracts/curation/CurationStorage.sol b/packages/contracts/contracts/curation/CurationStorage.sol index 12f5b255b..67b302bfe 100644 --- a/packages/contracts/contracts/curation/CurationStorage.sol +++ b/packages/contracts/contracts/curation/CurationStorage.sol @@ -1,16 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// solhint-disable one-contract-per-file pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; -import { ICuration } from "./ICuration.sol"; -import { IGraphCurationToken } from "./IGraphCurationToken.sol"; +import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; +import { IGraphCurationToken } from "@graphprotocol/interfaces/contracts/contracts/curation/IGraphCurationToken.sol"; import { Managed } from "../governance/Managed.sol"; /** * @title Curation Storage version 1 - * @dev This contract holds the first version of the storage variables + * @author Edge & Node + * @notice This contract holds the first version of the storage variables * for the Curation and L2Curation contracts. * When adding new variables, create a new version that inherits this and update * the contracts to use the new version instead. @@ -21,6 +26,9 @@ abstract contract CurationV1Storage is Managed, ICuration { /** * @dev CurationPool structure that holds the pool's state * for a particular subgraph deployment. + * @param tokens GRT Tokens stored as reserves for the subgraph deployment + * @param reserveRatio Ratio for the bonding curve, unused and deprecated in L2 where it will always be 100% but appear as 0 + * @param gcs Curation token contract for this curation pool */ struct CurationPool { uint256 tokens; // GRT Tokens stored as reserves for the subgraph deployment @@ -30,35 +38,36 @@ abstract contract CurationV1Storage is Managed, ICuration { // -- State -- - /// Tax charged when curators deposit funds. + /// @notice Tax charged when curators deposit funds. /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) uint32 public override curationTaxPercentage; - /// Default reserve ratio to configure curator shares bonding curve + /// @notice Default reserve ratio to configure curator shares bonding curve /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%). /// Unused in L2. uint32 public defaultReserveRatio; - /// Master copy address that holds implementation of curation token. + /// @notice Master copy address that holds implementation of curation token. /// @dev This is used as the target for GraphCurationToken clones. address public curationTokenMaster; - /// Minimum amount allowed to be deposited by curators to initialize a pool + /// @notice Minimum amount allowed to be deposited by curators to initialize a pool /// @dev This is the `startPoolBalance` for the bonding curve uint256 public minimumCurationDeposit; - /// Bonding curve library + /// @notice Bonding curve library /// Unused in L2. address public bondingCurve; - /// @dev Mapping of subgraphDeploymentID => CurationPool + /// @notice Mapping of subgraphDeploymentID => CurationPool /// There is only one CurationPool per SubgraphDeploymentID mapping(bytes32 => CurationPool) public pools; } /** * @title Curation Storage version 2 - * @dev This contract holds the second version of the storage variables + * @author Edge & Node + * @notice This contract holds the second version of the storage variables * for the Curation and L2Curation contracts. * It doesn't add new variables at this contract's level, but adds the Initializable * contract to the inheritance chain, which includes storage variables. @@ -71,6 +80,8 @@ abstract contract CurationV2Storage is CurationV1Storage, Initializable { /** * @title Curation Storage version 3 + * @author Edge & Node + * @notice This contract holds the third version of the storage variables for the Curation and L2Curation contracts * @dev This contract holds the third version of the storage variables * for the Curation and L2Curation contracts. * It adds a new variable subgraphService to the storage. @@ -78,6 +89,6 @@ abstract contract CurationV2Storage is CurationV1Storage, Initializable { * the contracts to use the new version instead. */ abstract contract CurationV3Storage is CurationV2Storage { - // Address of the subgraph service + /// @notice Address of the subgraph service address public subgraphService; } diff --git a/packages/contracts/contracts/curation/GraphCurationToken.sol b/packages/contracts/contracts/curation/GraphCurationToken.sol index 78b721e1b..108cc0680 100644 --- a/packages/contracts/contracts/curation/GraphCurationToken.sol +++ b/packages/contracts/contracts/curation/GraphCurationToken.sol @@ -2,13 +2,14 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "../governance/Governed.sol"; +import { Governed } from "../governance/Governed.sol"; /** * @title GraphCurationToken contract - * @dev This is the implementation of the Curation ERC20 token (GCS). + * @author Edge & Node + * @notice This is the implementation of the Curation ERC20 token (GCS). * * GCS are created for each subgraph deployment curated in the Curation contract. * The Curation contract is the owner of GCS tokens and the only one allowed to mint or @@ -20,7 +21,7 @@ import "../governance/Governed.sol"; */ contract GraphCurationToken is ERC20Upgradeable, Governed { /** - * @dev Graph Curation Token Contract initializer. + * @notice Graph Curation Token Contract initializer. * @param _owner Address of the contract issuing this token */ function initialize(address _owner) external initializer { @@ -29,7 +30,7 @@ contract GraphCurationToken is ERC20Upgradeable, Governed { } /** - * @dev Mint new tokens. + * @notice Mint new tokens. * @param _to Address to send the newly minted tokens * @param _amount Amount of tokens to mint */ @@ -38,7 +39,7 @@ contract GraphCurationToken is ERC20Upgradeable, Governed { } /** - * @dev Burn tokens from an address. + * @notice Burn tokens from an address. * @param _account Address from where tokens will be burned * @param _amount Amount of tokens to burn */ diff --git a/packages/contracts/contracts/curation/ICuration.sol b/packages/contracts/contracts/curation/ICuration.sol deleted file mode 100644 index 4f2c2bac5..000000000 --- a/packages/contracts/contracts/curation/ICuration.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -/** - * @title Curation Interface - * @dev Interface for the Curation contract (and L2Curation too) - */ -interface ICuration { - // -- Configuration -- - - /** - * @notice Update the default reserve ratio to `_defaultReserveRatio` - * @param _defaultReserveRatio Reserve ratio (in PPM) - */ - function setDefaultReserveRatio(uint32 _defaultReserveRatio) external; - - /** - * @notice Update the minimum deposit amount needed to intialize a new subgraph - * @param _minimumCurationDeposit Minimum amount of tokens required deposit - */ - function setMinimumCurationDeposit(uint256 _minimumCurationDeposit) external; - - /** - * @notice Set the curation tax percentage to charge when a curator deposits GRT tokens. - * @param _percentage Curation tax percentage charged when depositing GRT tokens - */ - function setCurationTaxPercentage(uint32 _percentage) external; - - /** - * @notice Set the master copy to use as clones for the curation token. - * @param _curationTokenMaster Address of implementation contract to use for curation tokens - */ - function setCurationTokenMaster(address _curationTokenMaster) external; - - // -- Curation -- - - /** - * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. - * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal - * @param _tokensIn Amount of Graph Tokens to deposit - * @param _signalOutMin Expected minimum amount of signal to receive - * @return Amount of signal minted - * @return Amount of curation tax burned - */ - function mint( - bytes32 _subgraphDeploymentID, - uint256 _tokensIn, - uint256 _signalOutMin - ) external returns (uint256, uint256); - - /** - * @notice Burn _signal from the SubgraphDeployment curation pool - * @param _subgraphDeploymentID SubgraphDeployment the curator is returning signal - * @param _signalIn Amount of signal to return - * @param _tokensOutMin Expected minimum amount of tokens to receive - * @return Tokens returned - */ - function burn(bytes32 _subgraphDeploymentID, uint256 _signalIn, uint256 _tokensOutMin) external returns (uint256); - - /** - * @notice Assign Graph Tokens collected as curation fees to the curation pool reserve. - * @param _subgraphDeploymentID SubgraphDeployment where funds should be allocated as reserves - * @param _tokens Amount of Graph Tokens to add to reserves - */ - function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external; - - // -- Getters -- - - /** - * @notice Check if any GRT tokens are deposited for a SubgraphDeployment. - * @param _subgraphDeploymentID SubgraphDeployment to check if curated - * @return True if curated, false otherwise - */ - function isCurated(bytes32 _subgraphDeploymentID) external view returns (bool); - - /** - * @notice Get the amount of signal a curator has in a curation pool. - * @param _curator Curator owning the signal tokens - * @param _subgraphDeploymentID Subgraph deployment curation pool - * @return Amount of signal owned by a curator for the subgraph deployment - */ - function getCuratorSignal(address _curator, bytes32 _subgraphDeploymentID) external view returns (uint256); - - /** - * @notice Get the amount of signal in a curation pool. - * @param _subgraphDeploymentID Subgraph deployment curation pool - * @return Amount of signal minted for the subgraph deployment - */ - function getCurationPoolSignal(bytes32 _subgraphDeploymentID) external view returns (uint256); - - /** - * @notice Get the amount of token reserves in a curation pool. - * @param _subgraphDeploymentID Subgraph deployment curation pool - * @return Amount of token reserves in the curation pool - */ - function getCurationPoolTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); - - /** - * @notice Calculate amount of signal that can be bought with tokens in a curation pool. - * This function considers and excludes the deposit tax. - * @param _subgraphDeploymentID Subgraph deployment to mint signal - * @param _tokensIn Amount of tokens used to mint signal - * @return Amount of signal that can be bought - * @return Amount of tokens that will be burned as curation tax - */ - function tokensToSignal(bytes32 _subgraphDeploymentID, uint256 _tokensIn) external view returns (uint256, uint256); - - /** - * @notice Calculate number of tokens to get when burning signal from a curation pool. - * @param _subgraphDeploymentID Subgraph deployment to burn signal - * @param _signalIn Amount of signal to burn - * @return Amount of tokens to get for the specified amount of signal - */ - function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn) external view returns (uint256); - - /** - * @notice Tax charged when curators deposit funds. - * Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) - * @return Curation tax percentage expressed in PPM - */ - function curationTaxPercentage() external view returns (uint32); -} diff --git a/packages/contracts/contracts/curation/IGraphCurationToken.sol b/packages/contracts/contracts/curation/IGraphCurationToken.sol deleted file mode 100644 index 43679aba6..000000000 --- a/packages/contracts/contracts/curation/IGraphCurationToken.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; - -interface IGraphCurationToken is IERC20Upgradeable { - function initialize(address _owner) external; - - function burnFrom(address _account, uint256 _amount) external; - - function mint(address _to, uint256 _amount) external; -} diff --git a/packages/contracts/contracts/discovery/GNS.sol b/packages/contracts/contracts/discovery/GNS.sol index 3cbb9ca8a..384bd3b66 100644 --- a/packages/contracts/contracts/discovery/GNS.sol +++ b/packages/contracts/contracts/discovery/GNS.sol @@ -3,22 +3,26 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-indexed-events, gas-small-strings, gas-strict-inequalities + import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { Multicall } from "../base/Multicall.sol"; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; import { TokenUtils } from "../utils/TokenUtils.sol"; -import { ICuration } from "../curation/ICuration.sol"; +import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; import { Managed } from "../governance/Managed.sol"; -import { ISubgraphNFT } from "./ISubgraphNFT.sol"; +import { ISubgraphNFT } from "@graphprotocol/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol"; -import { IGNS } from "./IGNS.sol"; +import { IGNS } from "@graphprotocol/interfaces/contracts/contracts/discovery/IGNS.sol"; import { GNSV3Storage } from "./GNSStorage.sol"; /** * @title GNS - * @dev The Graph Name System contract provides a decentralized naming system for subgraphs + * @author Edge & Node + * @notice The Graph Name System contract provides a decentralized naming system for subgraphs * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. * Each version is associated with a Subgraph Deployment. The contract has no knowledge of * human-readable names. All human readable names emitted in events. @@ -34,15 +38,21 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { uint32 private constant MAX_PPM = 1000000; /// @dev Equates to Connector weight on bancor formula to be CW = 1 + // solhint-disable-next-line immutable-vars-naming uint32 internal immutable fixedReserveRatio = MAX_PPM; // -- Events -- - /// @dev Emitted when the subgraph NFT contract is updated + /// @notice Emitted when the subgraph NFT contract is updated + /// @param subgraphNFT Address of the new subgraph NFT contract event SubgraphNFTUpdated(address subgraphNFT); /** - * @dev Emitted when graph account sets its default name + * @notice Emitted when graph account sets its default name + * @param graphAccount Address of the graph account + * @param nameSystem Name system identifier (only ENS for now) + * @param nameIdentifier Name identifier in the name system + * @param name Human-readable name */ event SetDefaultName( address indexed graphAccount, @@ -52,12 +62,17 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when the subgraph metadata is updated. + * @notice Emitted when the subgraph metadata is updated. + * @param subgraphID ID of the subgraph + * @param subgraphMetadata IPFS hash of the subgraph metadata */ event SubgraphMetadataUpdated(uint256 indexed subgraphID, bytes32 subgraphMetadata); /** - * @dev Emitted when a subgraph version is updated. + * @notice Emitted when a subgraph version is updated. + * @param subgraphID ID of the subgraph + * @param subgraphDeploymentID Subgraph deployment ID for the new version + * @param versionMetadata IPFS hash of the version metadata */ event SubgraphVersionUpdated( uint256 indexed subgraphID, @@ -66,7 +81,12 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when a curator mints signal. + * @notice Emitted when a curator mints signal. + * @param subgraphID ID of the subgraph + * @param curator Address of the curator + * @param nSignalCreated Amount of name signal created + * @param vSignalCreated Amount of version signal created + * @param tokensDeposited Amount of tokens deposited */ event SignalMinted( uint256 indexed subgraphID, @@ -77,7 +97,12 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when a curator burns signal. + * @notice Emitted when a curator burns signal. + * @param subgraphID ID of the subgraph + * @param curator Address of the curator + * @param nSignalBurnt Amount of name signal burned + * @param vSignalBurnt Amount of version signal burned + * @param tokensReceived Amount of tokens received */ event SignalBurned( uint256 indexed subgraphID, @@ -88,7 +113,11 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when a curator transfers signal. + * @notice Emitted when a curator transfers signal. + * @param subgraphID ID of the subgraph + * @param from Address transferring the signal + * @param to Address receiving the signal + * @param nSignalTransferred Amount of name signal transferred */ event SignalTransferred( uint256 indexed subgraphID, @@ -98,14 +127,21 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when a subgraph is created. + * @notice Emitted when a subgraph is created. + * @param subgraphID ID of the subgraph + * @param subgraphDeploymentID Subgraph deployment ID + * @param reserveRatio Reserve ratio for the bonding curve */ event SubgraphPublished(uint256 indexed subgraphID, bytes32 indexed subgraphDeploymentID, uint32 reserveRatio); /** - * @dev Emitted when a subgraph is upgraded to point to a new + * @notice Emitted when a subgraph is upgraded to point to a new * subgraph deployment, burning all the old vSignal and depositing the GRT into the * new vSignal curve. + * @param subgraphID ID of the subgraph + * @param vSignalCreated Amount of version signal created in the new deployment + * @param tokensSignalled Amount of tokens signalled in the new deployment + * @param subgraphDeploymentID New subgraph deployment ID */ event SubgraphUpgraded( uint256 indexed subgraphID, @@ -115,29 +151,39 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when a subgraph is deprecated. + * @notice Emitted when a subgraph is deprecated. + * @param subgraphID ID of the subgraph + * @param withdrawableGRT Amount of GRT available for withdrawal */ event SubgraphDeprecated(uint256 indexed subgraphID, uint256 withdrawableGRT); /** - * @dev Emitted when a curator withdraws GRT from a deprecated subgraph + * @notice Emitted when a curator withdraws GRT from a deprecated subgraph + * @param subgraphID ID of the subgraph + * @param curator Address of the curator + * @param nSignalBurnt Amount of name signal burned + * @param withdrawnGRT Amount of GRT withdrawn */ event GRTWithdrawn(uint256 indexed subgraphID, address indexed curator, uint256 nSignalBurnt, uint256 withdrawnGRT); /** - * @dev Emitted when the counterpart (L1/L2) GNS address is updated + * @notice Emitted when the counterpart (L1/L2) GNS address is updated + * @param _counterpart Address of the counterpart GNS contract */ event CounterpartGNSAddressUpdated(address _counterpart); // -- Modifiers -- /** - * @dev Emitted when a legacy subgraph is claimed + * @notice Emitted when a legacy subgraph is claimed + * @param graphAccount Address of the graph account that created the subgraph + * @param subgraphNumber Sequence number of the subgraph */ event LegacySubgraphClaimed(address indexed graphAccount, uint256 subgraphNumber); /** - * @dev Modifier that allows only a subgraph operator to be the caller + * @notice Modifier that allows only a subgraph operator to be the caller + * @param _subgraphID ID of the subgraph to check authorization for */ modifier onlySubgraphAuth(uint256 _subgraphID) { require(ownerOf(_subgraphID) == msg.sender, "GNS: Must be authorized"); @@ -160,7 +206,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Approve curation contract to pull funds. + * @inheritdoc IGNS */ function approveAll() external override { graphToken().approve(address(curation()), type(uint256).max); @@ -169,9 +215,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { // -- Config -- /** - * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all - * the name curators tokens while upgrading or deprecating and is configurable in parts per million. - * @param _ownerTaxPercentage Owner tax percentage + * @inheritdoc IGNS */ function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external override onlyGovernor { _setOwnerTaxPercentage(_ownerTaxPercentage); @@ -200,11 +244,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { // -- Actions -- /** - * @notice Allows a graph account to set a default name - * @param _graphAccount Account that is setting its name - * @param _nameSystem Name system account already has ownership of a name in - * @param _nameIdentifier The unique identifier that is used to identify the name in the system - * @param _name The name being set as default + * @inheritdoc IGNS */ function setDefaultName( address _graphAccount, @@ -217,9 +257,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Allows a subgraph owner to update the metadata of a subgraph they have published - * @param _subgraphID Subgraph ID - * @param _subgraphMetadata IPFS hash for the subgraph metadata + * @inheritdoc IGNS */ function updateSubgraphMetadata( uint256 _subgraphID, @@ -229,10 +267,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Publish a new subgraph. - * @param _subgraphDeploymentID Subgraph deployment for the subgraph - * @param _versionMetadata IPFS hash for the subgraph version metadata - * @param _subgraphMetadata IPFS hash for the subgraph metadata + * @inheritdoc IGNS */ function publishNewSubgraph( bytes32 _subgraphDeploymentID, @@ -261,10 +296,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Publish a new version of an existing subgraph. - * @param _subgraphID Subgraph ID - * @param _subgraphDeploymentID Subgraph deployment ID of the new version - * @param _versionMetadata IPFS hash for the subgraph version metadata + * @inheritdoc IGNS */ function publishNewVersion( uint256 _subgraphID, @@ -322,10 +354,10 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * @inheritdoc IGNS + * @notice The bonding curve is destroyed, the vSignal is burned, and the GNS * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. * Can only be done by the subgraph owner. - * @param _subgraphID Subgraph ID */ function deprecateSubgraph(uint256 _subgraphID) external override notPaused onlySubgraphAuth(_subgraphID) { // Subgraph check @@ -350,10 +382,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Deposit GRT into a subgraph and mint signal. - * @param _subgraphID Subgraph ID - * @param _tokensIn The amount of tokens the nameCurator wants to deposit - * @param _nSignalOutMin Expected minimum amount of name signal to receive + * @inheritdoc IGNS */ function mintSignal( uint256 _subgraphID, @@ -383,10 +412,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Burn signal for a subgraph and return the GRT. - * @param _subgraphID Subgraph ID - * @param _nSignal The amount of nSignal the nameCurator wants to burn - * @param _tokensOutMin Expected minimum amount of tokens to receive + * @inheritdoc IGNS */ function burnSignal( uint256 _subgraphID, @@ -543,7 +569,9 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { * @notice Calculate subgraph signal to be returned for an amount of tokens. * @param _subgraphID Subgraph ID * @param _tokensIn Tokens being exchanged for subgraph signal - * @return Amount of subgraph signal and curation tax + * @return nSignalOut Amount of name signal minted + * @return curationTax Amount of curation tax charged + * @return vSignalOut Amount of version signal minted */ function tokensToNSignal( uint256 _subgraphID, @@ -562,7 +590,8 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { * @notice Calculate tokens returned for an amount of subgraph signal. * @param _subgraphID Subgraph ID * @param _nSignalIn Subgraph signal being exchanged for tokens - * @return Amount of tokens returned for an amount of subgraph signal + * @return vSignalOut Amount of version signal burned + * @return tokensOut Amount of tokens returned */ function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) public view override returns (uint256, uint256) { // Get subgraph or revert if not published @@ -574,10 +603,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. - * @param _subgraphID Subgraph ID - * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal - * @return Amount of subgraph signal that can be bought + * @inheritdoc IGNS */ function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) public view override returns (uint256) { SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); @@ -591,10 +617,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. - * @param _subgraphID Subgraph ID - * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal - * @return Amount of subgraph deployment signal that can be returned + * @inheritdoc IGNS */ function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) public view override returns (uint256) { SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); @@ -602,29 +625,21 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Get the amount of subgraph signal a curator has. - * @param _subgraphID Subgraph ID - * @param _curator Curator address - * @return Amount of subgraph signal owned by a curator + * @inheritdoc IGNS */ function getCuratorSignal(uint256 _subgraphID, address _curator) public view override returns (uint256) { return _getSubgraphData(_subgraphID).curatorNSignal[_curator]; } /** - * @notice Return whether a subgraph is published. - * @param _subgraphID Subgraph ID - * @return Return true if subgraph is currently published + * @inheritdoc IGNS */ function isPublished(uint256 _subgraphID) public view override returns (bool) { return _isPublished(_getSubgraphData(_subgraphID)); } /** - * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). - * @param _subgraphID Subgraph ID - * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) - * @return seqID Sequence number for the subgraph + * @inheritdoc IGNS */ function getLegacySubgraphKey(uint256 _subgraphID) public view override returns (address account, uint256 seqID) { LegacySubgraphKey storage legacySubgraphKey = legacySubgraphKeys[_subgraphID]; @@ -633,16 +648,14 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @notice Return the owner of a subgraph. - * @param _tokenID Subgraph ID - * @return Owner address + * @inheritdoc IGNS */ function ownerOf(uint256 _tokenID) public view override returns (address) { return subgraphNFT.ownerOf(_tokenID); } /** - * @dev Calculate tax that owner will have to cover for upgrading or deprecating. + * @notice Calculate tax that owner will have to cover for upgrading or deprecating. * @param _tokens Tokens that were received from deprecating the old subgraph * @param _owner Subgraph owner * @param _curationTaxPercentage Tax percentage on curation deposits from Curation contract @@ -689,8 +702,9 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Return the next subgraphID given the account that is creating the subgraph. + * @notice Return the next subgraphID given the account that is creating the subgraph. * NOTE: This function updates the sequence ID for the account + * @param _account The account creating the subgraph * @return Sequence ID for the account */ function _nextSubgraphID(address _account) internal returns (uint256) { @@ -698,8 +712,9 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Return a new consecutive sequence ID for an account and update to the next value. + * @notice Return a new consecutive sequence ID for an account and update to the next value. * NOTE: This function updates the sequence ID for the account + * @param _account The account to get the next sequence ID for * @return Sequence ID for the account */ function _nextAccountSeqID(address _account) internal returns (uint256) { @@ -709,7 +724,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Mint the NFT for the subgraph. + * @notice Mint the NFT for the subgraph. * @param _owner Owner address * @param _tokenID Subgraph ID */ @@ -718,7 +733,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Burn the NFT for the subgraph. + * @notice Burn the NFT for the subgraph. * @param _tokenID Subgraph ID */ function _burnNFT(uint256 _tokenID) internal { @@ -726,7 +741,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Set the subgraph metadata. + * @notice Set the subgraph metadata. * @param _tokenID Subgraph ID * @param _subgraphMetadata IPFS hash of the subgraph metadata */ @@ -739,7 +754,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Get subgraph data. + * @notice Get subgraph data. * This function will first look for a v1 subgraph and return it if found. * @param _subgraphID Subgraph ID * @return Subgraph Data @@ -755,7 +770,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Return whether a subgraph is published. + * @notice Return whether a subgraph is published. * @param _subgraphData Subgraph Data * @return Return true if subgraph is currently published */ @@ -764,7 +779,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Return the subgraph data or revert if not published or deprecated. + * @notice Return the subgraph data or revert if not published or deprecated. * @param _subgraphID Subgraph ID * @return Subgraph Data */ @@ -775,9 +790,11 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Build a subgraph ID based on the account creating it and a sequence number for that account. + * @notice Build a subgraph ID based on the account creating it and a sequence number for that account. * Only used for legacy subgraphs being migrated, as new ones will also use the chainid. * Subgraph ID is the keccak hash of account+seqID + * @param _account The account creating the subgraph + * @param _seqID The sequence ID for the account * @return Subgraph ID */ function _buildLegacySubgraphID(address _account, uint256 _seqID) internal pure returns (uint256) { @@ -785,8 +802,10 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Build a subgraph ID based on the account creating it and a sequence number for that account. + * @notice Build a subgraph ID based on the account creating it and a sequence number for that account. * Subgraph ID is the keccak hash of account+seqID + * @param _account The account creating the subgraph + * @param _seqID The sequence ID for the account * @return Subgraph ID */ function _buildSubgraphID(address _account, uint256 _seqID) internal pure returns (uint256) { @@ -800,7 +819,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all + * @notice Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all * the name curators tokens while upgrading or deprecating and is configurable in parts per million. * @param _ownerTaxPercentage Owner tax percentage */ @@ -811,7 +830,7 @@ abstract contract GNS is GNSV3Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Internal: Set the NFT registry contract + * @notice Internal: Set the NFT registry contract * @param _subgraphNFT Address of the ERC721 contract */ function _setSubgraphNFT(address _subgraphNFT) private { diff --git a/packages/contracts/contracts/discovery/GNSStorage.sol b/packages/contracts/contracts/discovery/GNSStorage.sol index 80122c9ba..ca746ec29 100644 --- a/packages/contracts/contracts/discovery/GNSStorage.sol +++ b/packages/contracts/contracts/discovery/GNSStorage.sol @@ -1,23 +1,28 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// solhint-disable one-contract-per-file pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; import { Managed } from "../governance/Managed.sol"; -import { IEthereumDIDRegistry } from "./erc1056/IEthereumDIDRegistry.sol"; -import { IGNS } from "./IGNS.sol"; -import { ISubgraphNFT } from "./ISubgraphNFT.sol"; +import { IEthereumDIDRegistry } from "@graphprotocol/interfaces/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol"; +import { IGNS } from "@graphprotocol/interfaces/contracts/contracts/discovery/IGNS.sol"; +import { ISubgraphNFT } from "@graphprotocol/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol"; /** * @title GNSV1Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the GNS contract, version 1 */ abstract contract GNSV1Storage is Managed { // -- State -- - /// Percentage of curation tax that must be paid by the owner, in parts per million. + /// @notice Percentage of curation tax that must be paid by the owner, in parts per million. uint32 public ownerTaxPercentage; /// @dev [DEPRECATED] Bonding curve formula. @@ -29,11 +34,11 @@ abstract contract GNSV1Storage is Managed { /// (graphAccountID, subgraphNumber) => subgraphDeploymentID mapping(address => mapping(uint256 => bytes32)) internal legacySubgraphs; - /// Every time an account creates a subgraph it increases a per-account sequence ID. + /// @notice Every time an account creates a subgraph it increases a per-account sequence ID. /// account => seqID mapping(address => uint256) public nextAccountSeqID; - /// Stores all the signal deposited on a legacy subgraph. + /// @notice Stores all the signal deposited on a legacy subgraph. /// (graphAccountID, subgraphNumber) => SubgraphData mapping(address => mapping(uint256 => IGNS.SubgraphData)) public legacySubgraphData; @@ -44,31 +49,33 @@ abstract contract GNSV1Storage is Managed { /** * @title GNSV2Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the GNS contract, version 2 */ abstract contract GNSV2Storage is GNSV1Storage { - /// Stores the account and seqID for a legacy subgraph that has been migrated. + /// @notice Stores the account and seqID for a legacy subgraph that has been migrated. /// Use it whenever a legacy (v1) subgraph NFT was claimed to maintain compatibility. /// Keep a reference from subgraphID => (graphAccount, subgraphNumber) mapping(uint256 => IGNS.LegacySubgraphKey) public legacySubgraphKeys; - /// Store data for all NFT-based (v2) subgraphs. + /// @notice Store data for all NFT-based (v2) subgraphs. /// subgraphID => SubgraphData mapping(uint256 => IGNS.SubgraphData) public subgraphs; - /// Contract that represents subgraph ownership through an NFT + /// @notice Contract that represents subgraph ownership through an NFT ISubgraphNFT public subgraphNFT; } /** * @title GNSV3Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the base GNS contract, version 3. * @dev Note that this is the first version that includes a storage gap - if adding * future versions, make sure to move the gap to the new version and * reduce the size of the gap accordingly. */ abstract contract GNSV3Storage is GNSV2Storage, Initializable { - /// Address of the counterpart GNS contract (L1GNS/L2GNS) + /// @notice Address of the counterpart GNS contract (L1GNS/L2GNS) address public counterpartGNSAddress; /// @dev Gap to allow adding variables in future upgrades (since L1GNS and L2GNS have their own storage as well) uint256[50] private __gap; diff --git a/packages/contracts/contracts/discovery/IGNS.sol b/packages/contracts/contracts/discovery/IGNS.sol deleted file mode 100644 index 70b366d9b..000000000 --- a/packages/contracts/contracts/discovery/IGNS.sol +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -/** - * @title Interface for GNS - */ -interface IGNS { - // -- Pool -- - - /** - * @dev The SubgraphData struct holds information about subgraphs - * and their signal; both nSignal (i.e. name signal at the GNS level) - * and vSignal (i.e. version signal at the Curation contract level) - */ - struct SubgraphData { - uint256 vSignal; // The token of the subgraph-deployment bonding curve - uint256 nSignal; // The token of the subgraph bonding curve - mapping(address => uint256) curatorNSignal; - bytes32 subgraphDeploymentID; - uint32 __DEPRECATED_reserveRatio; // solhint-disable-line var-name-mixedcase - bool disabled; - uint256 withdrawableGRT; - } - - /** - * @dev The LegacySubgraphKey struct holds the account and sequence ID - * used to generate subgraph IDs in legacy subgraphs. - */ - struct LegacySubgraphKey { - address account; - uint256 accountSeqID; - } - - // -- Configuration -- - - /** - * @notice Approve curation contract to pull funds. - */ - function approveAll() external; - - /** - * @notice Set the owner fee percentage. This is used to prevent a subgraph owner to drain all - * the name curators tokens while upgrading or deprecating and is configurable in parts per million. - * @param _ownerTaxPercentage Owner tax percentage - */ - function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; - - // -- Publishing -- - - /** - * @notice Allows a graph account to set a default name - * @param _graphAccount Account that is setting its name - * @param _nameSystem Name system account already has ownership of a name in - * @param _nameIdentifier The unique identifier that is used to identify the name in the system - * @param _name The name being set as default - */ - function setDefaultName( - address _graphAccount, - uint8 _nameSystem, - bytes32 _nameIdentifier, - string calldata _name - ) external; - - /** - * @notice Allows a subgraph owner to update the metadata of a subgraph they have published - * @param _subgraphID Subgraph ID - * @param _subgraphMetadata IPFS hash for the subgraph metadata - */ - function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; - - /** - * @notice Publish a new subgraph. - * @param _subgraphDeploymentID Subgraph deployment for the subgraph - * @param _versionMetadata IPFS hash for the subgraph version metadata - * @param _subgraphMetadata IPFS hash for the subgraph metadata - */ - function publishNewSubgraph( - bytes32 _subgraphDeploymentID, - bytes32 _versionMetadata, - bytes32 _subgraphMetadata - ) external; - - /** - * @notice Publish a new version of an existing subgraph. - * @param _subgraphID Subgraph ID - * @param _subgraphDeploymentID Subgraph deployment ID of the new version - * @param _versionMetadata IPFS hash for the subgraph version metadata - */ - function publishNewVersion(uint256 _subgraphID, bytes32 _subgraphDeploymentID, bytes32 _versionMetadata) external; - - /** - * @notice Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS - * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. - * Can only be done by the subgraph owner. - * @param _subgraphID Subgraph ID - */ - function deprecateSubgraph(uint256 _subgraphID) external; - - // -- Curation -- - - /** - * @notice Deposit GRT into a subgraph and mint signal. - * @param _subgraphID Subgraph ID - * @param _tokensIn The amount of tokens the nameCurator wants to deposit - * @param _nSignalOutMin Expected minimum amount of name signal to receive - */ - function mintSignal(uint256 _subgraphID, uint256 _tokensIn, uint256 _nSignalOutMin) external; - - /** - * @notice Burn signal for a subgraph and return the GRT. - * @param _subgraphID Subgraph ID - * @param _nSignal The amount of nSignal the nameCurator wants to burn - * @param _tokensOutMin Expected minimum amount of tokens to receive - */ - function burnSignal(uint256 _subgraphID, uint256 _nSignal, uint256 _tokensOutMin) external; - - /** - * @notice Move subgraph signal from sender to `_recipient` - * @param _subgraphID Subgraph ID - * @param _recipient Address to send the signal to - * @param _amount The amount of nSignal to transfer - */ - function transferSignal(uint256 _subgraphID, address _recipient, uint256 _amount) external; - - /** - * @notice Withdraw tokens from a deprecated subgraph. - * When the subgraph is deprecated, any curator can call this function and - * withdraw the GRT they are entitled for its original deposit - * @param _subgraphID Subgraph ID - */ - function withdraw(uint256 _subgraphID) external; - - // -- Getters -- - - /** - * @notice Return the owner of a subgraph. - * @param _tokenID Subgraph ID - * @return Owner address - */ - function ownerOf(uint256 _tokenID) external view returns (address); - - /** - * @notice Return the total signal on the subgraph. - * @param _subgraphID Subgraph ID - * @return Total signal on the subgraph - */ - function subgraphSignal(uint256 _subgraphID) external view returns (uint256); - - /** - * @notice Return the total tokens on the subgraph at current value. - * @param _subgraphID Subgraph ID - * @return Total tokens on the subgraph - */ - function subgraphTokens(uint256 _subgraphID) external view returns (uint256); - - /** - * @notice Calculate subgraph signal to be returned for an amount of tokens. - * @param _subgraphID Subgraph ID - * @param _tokensIn Tokens being exchanged for subgraph signal - * @return Amount of subgraph signal and curation tax - */ - function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) external view returns (uint256, uint256, uint256); - - /** - * @notice Calculate tokens returned for an amount of subgraph signal. - * @param _subgraphID Subgraph ID - * @param _nSignalIn Subgraph signal being exchanged for tokens - * @return Amount of tokens returned for an amount of subgraph signal - */ - function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) external view returns (uint256, uint256); - - /** - * @notice Calculate subgraph signal to be returned for an amount of subgraph deployment signal. - * @param _subgraphID Subgraph ID - * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal - * @return Amount of subgraph signal that can be bought - */ - function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) external view returns (uint256); - - /** - * @notice Calculate subgraph deployment signal to be returned for an amount of subgraph signal. - * @param _subgraphID Subgraph ID - * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal - * @return Amount of subgraph deployment signal that can be returned - */ - function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) external view returns (uint256); - - /** - * @notice Get the amount of subgraph signal a curator has. - * @param _subgraphID Subgraph ID - * @param _curator Curator address - * @return Amount of subgraph signal owned by a curator - */ - function getCuratorSignal(uint256 _subgraphID, address _curator) external view returns (uint256); - - /** - * @notice Return whether a subgraph is published. - * @param _subgraphID Subgraph ID - * @return Return true if subgraph is currently published - */ - function isPublished(uint256 _subgraphID) external view returns (bool); - - /** - * @notice Return whether a subgraph is a legacy subgraph (created before subgraph NFTs). - * @param _subgraphID Subgraph ID - * @return Return true if subgraph is a legacy subgraph - */ - function isLegacySubgraph(uint256 _subgraphID) external view returns (bool); - - /** - * @notice Returns account and sequence ID for a legacy subgraph (created before subgraph NFTs). - * @param _subgraphID Subgraph ID - * @return account Account that created the subgraph (or 0 if it's not a legacy subgraph) - * @return seqID Sequence number for the subgraph - */ - function getLegacySubgraphKey(uint256 _subgraphID) external view returns (address account, uint256 seqID); -} diff --git a/packages/contracts/contracts/discovery/IServiceRegistry.sol b/packages/contracts/contracts/discovery/IServiceRegistry.sol deleted file mode 100644 index 724f7bebe..000000000 --- a/packages/contracts/contracts/discovery/IServiceRegistry.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IServiceRegistry { - struct IndexerService { - string url; - string geohash; - } - - function register(string calldata _url, string calldata _geohash) external; - - function registerFor(address _indexer, string calldata _url, string calldata _geohash) external; - - function unregister() external; - - function unregisterFor(address _indexer) external; - - function isRegistered(address _indexer) external view returns (bool); -} diff --git a/packages/contracts/contracts/discovery/ISubgraphNFT.sol b/packages/contracts/contracts/discovery/ISubgraphNFT.sol deleted file mode 100644 index 6cef69297..000000000 --- a/packages/contracts/contracts/discovery/ISubgraphNFT.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -interface ISubgraphNFT is IERC721 { - // -- Config -- - - function setMinter(address _minter) external; - - function setTokenDescriptor(address _tokenDescriptor) external; - - function setBaseURI(string memory _baseURI) external; - - // -- Actions -- - - function mint(address _to, uint256 _tokenId) external; - - function burn(uint256 _tokenId) external; - - function setSubgraphMetadata(uint256 _tokenId, bytes32 _subgraphMetadata) external; - - function tokenURI(uint256 _tokenId) external view returns (string memory); -} diff --git a/packages/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol b/packages/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol deleted file mode 100644 index cd0785dcb..000000000 --- a/packages/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6; - -/// @title Describes subgraph NFT tokens via URI -interface ISubgraphNFTDescriptor { - /// @notice Produces the URI describing a particular token ID for a Subgraph - /// @dev Note this URI may be data: URI with the JSON contents directly inlined - /// @param _minter Address of the allowed minter - /// @param _tokenId The ID of the subgraph NFT for which to produce a description, which may not be valid - /// @param _baseURI The base URI that could be prefixed to the final URI - /// @param _subgraphMetadata Subgraph metadata set for the subgraph - /// @return The URI of the ERC721-compliant metadata - function tokenURI( - address _minter, - uint256 _tokenId, - string calldata _baseURI, - bytes32 _subgraphMetadata - ) external view returns (string memory); -} diff --git a/packages/contracts/contracts/discovery/L1GNS.sol b/packages/contracts/contracts/discovery/L1GNS.sol index 31e9b0fb3..3441d05fa 100644 --- a/packages/contracts/contracts/discovery/L1GNS.sol +++ b/packages/contracts/contracts/discovery/L1GNS.sol @@ -7,14 +7,15 @@ import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/Sa import { GNS } from "./GNS.sol"; -import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; -import { IL2GNS } from "../l2/discovery/IL2GNS.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; +import { IL2GNS } from "@graphprotocol/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { L1GNSV1Storage } from "./L1GNSStorage.sol"; /** * @title L1GNS - * @dev The Graph Name System contract provides a decentralized naming system for subgraphs + * @author Edge & Node + * @notice The Graph Name System contract provides a decentralized naming system for subgraphs * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. * Each version is associated with a Subgraph Deployment. The contract has no knowledge of * human-readable names. All human readable names emitted in events. @@ -25,7 +26,13 @@ import { L1GNSV1Storage } from "./L1GNSStorage.sol"; contract L1GNS is GNS, L1GNSV1Storage { using SafeMathUpgradeable for uint256; - /// @dev Emitted when a subgraph was sent to L2 through the bridge + /** + * @notice Emitted when a subgraph was sent to L2 through the bridge + * @param _subgraphID ID of the subgraph being transferred + * @param _l1Owner Address of the subgraph owner on L1 + * @param _l2Owner Address that will own the subgraph on L2 + * @param _tokens Amount of tokens transferred with the subgraph + */ event SubgraphSentToL2( uint256 indexed _subgraphID, address indexed _l1Owner, @@ -33,7 +40,13 @@ contract L1GNS is GNS, L1GNSV1Storage { uint256 _tokens ); - /// @dev Emitted when a curator's balance for a subgraph was sent to L2 + /** + * @notice Emitted when a curator's balance for a subgraph was sent to L2 + * @param _subgraphID ID of the subgraph + * @param _l1Curator Address of the curator on L1 + * @param _l2Beneficiary Address that will receive the tokens on L2 + * @param _tokens Amount of tokens transferred + */ event CuratorBalanceSentToL2( uint256 indexed _subgraphID, address indexed _l1Curator, @@ -42,10 +55,10 @@ contract L1GNS is GNS, L1GNSV1Storage { ); /** - * @notice Send a subgraph's data and tokens to L2. - * Use the Arbitrum SDK to estimate the L2 retryable ticket parameters. + * @notice Send a subgraph's data and tokens to L2 + * @dev Use the Arbitrum SDK to estimate the L2 retryable ticket parameters. * Note that any L2 gas/fee refunds will be lost, so the function only accepts - * the exact amount of ETH to cover _maxSubmissionCost + _maxGas * _gasPriceBid. + * the exact amount of ETH to cover _maxSubmissionCost + _maxGas * _gasPriceBid * @param _subgraphID Subgraph ID * @param _l2Owner Address that will own the subgraph in L2 (could be the L1 owner, but could be different if the L1 owner is an L1 contract) * @param _maxGas Max gas to use for the L2 retryable ticket diff --git a/packages/contracts/contracts/discovery/L1GNSStorage.sol b/packages/contracts/contracts/discovery/L1GNSStorage.sol index 557814513..72af676f2 100644 --- a/packages/contracts/contracts/discovery/L1GNSStorage.sol +++ b/packages/contracts/contracts/discovery/L1GNSStorage.sol @@ -1,16 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6; pragma abicoder v2; /** * @title L1GNSV1Storage + * @author Edge & Node * @notice This contract holds all the L1-specific storage variables for the L1GNS contract, version 1 * @dev When adding new versions, make sure to move the gap to the new version and * reduce the size of the gap accordingly. */ abstract contract L1GNSV1Storage { - /// True for subgraph IDs that have been transferred to L2 + /// @notice True for subgraph IDs that have been transferred to L2 mapping(uint256 => bool) public subgraphTransferredToL2; /// @dev Storage gap to keep storage slots fixed in future versions uint256[50] private __gap; diff --git a/packages/contracts/contracts/discovery/ServiceRegistry.sol b/packages/contracts/contracts/discovery/ServiceRegistry.sol index 1eb1393d3..32db1fe1b 100644 --- a/packages/contracts/contracts/discovery/ServiceRegistry.sol +++ b/packages/contracts/contracts/discovery/ServiceRegistry.sol @@ -3,58 +3,68 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "../governance/Managed.sol"; -import "../upgrades/GraphUpgradeable.sol"; +import { Managed } from "../governance/Managed.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; -import "./ServiceRegistryStorage.sol"; -import "./IServiceRegistry.sol"; +import { ServiceRegistryV1Storage } from "./ServiceRegistryStorage.sol"; +import { IServiceRegistry } from "@graphprotocol/interfaces/contracts/contracts/discovery/IServiceRegistry.sol"; /** * @title ServiceRegistry contract - * @dev This contract supports the service discovery process by allowing indexers to + * @author Edge & Node + * @notice This contract supports the service discovery process by allowing indexers to * register their service url and any other relevant information. */ contract ServiceRegistry is ServiceRegistryV1Storage, GraphUpgradeable, IServiceRegistry { // -- Events -- + /** + * @notice Emitted when an indexer registers their service + * @param indexer Address of the indexer + * @param url URL of the indexer service + * @param geohash Geohash of the indexer service location + */ event ServiceRegistered(address indexed indexer, string url, string geohash); + + /** + * @notice Emitted when an indexer unregisters their service + * @param indexer Address of the indexer + */ event ServiceUnregistered(address indexed indexer); /** - * @dev Check if the caller is authorized (indexer or operator) + * @notice Check if the caller is authorized (indexer or operator) + * @param _indexer Address of the indexer to check authorization for + * @return True if the caller is authorized, false otherwise */ function _isAuth(address _indexer) internal view returns (bool) { return msg.sender == _indexer || staking().isOperator(msg.sender, _indexer) == true; } /** - * @dev Initialize this contract. + * @notice Initialize this contract. + * @param _controller Address of the controller contract */ function initialize(address _controller) external onlyImpl { Managed._initialize(_controller); } /** - * @dev Register an indexer service - * @param _url URL of the indexer service - * @param _geohash Geohash of the indexer service location + * @inheritdoc IServiceRegistry */ function register(string calldata _url, string calldata _geohash) external override { _register(msg.sender, _url, _geohash); } /** - * @dev Register an indexer service - * @param _indexer Address of the indexer - * @param _url URL of the indexer service - * @param _geohash Geohash of the indexer service location + * @inheritdoc IServiceRegistry */ function registerFor(address _indexer, string calldata _url, string calldata _geohash) external override { _register(_indexer, _url, _geohash); } /** - * @dev Internal: Register an indexer service + * @notice Internal: Register an indexer service * @param _indexer Address of the indexer * @param _url URL of the indexer service * @param _geohash Geohash of the indexer service location @@ -69,22 +79,21 @@ contract ServiceRegistry is ServiceRegistryV1Storage, GraphUpgradeable, IService } /** - * @dev Unregister an indexer service + * @inheritdoc IServiceRegistry */ function unregister() external override { _unregister(msg.sender); } /** - * @dev Unregister an indexer service - * @param _indexer Address of the indexer + * @inheritdoc IServiceRegistry */ function unregisterFor(address _indexer) external override { _unregister(_indexer); } /** - * @dev Unregister an indexer service + * @notice Unregister an indexer service * @param _indexer Address of the indexer */ function _unregister(address _indexer) private { @@ -96,8 +105,7 @@ contract ServiceRegistry is ServiceRegistryV1Storage, GraphUpgradeable, IService } /** - * @dev Return the registration status of an indexer service - * @return True if the indexer service is registered + * @inheritdoc IServiceRegistry */ function isRegistered(address _indexer) public view override returns (bool) { return bytes(services[_indexer].url).length > 0; diff --git a/packages/contracts/contracts/discovery/ServiceRegistryStorage.sol b/packages/contracts/contracts/discovery/ServiceRegistryStorage.sol index 1cd484970..4ad8a7359 100644 --- a/packages/contracts/contracts/discovery/ServiceRegistryStorage.sol +++ b/packages/contracts/contracts/discovery/ServiceRegistryStorage.sol @@ -1,13 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6; -import "../governance/Managed.sol"; +import { Managed } from "../governance/Managed.sol"; -import "./IServiceRegistry.sol"; +import { IServiceRegistry } from "@graphprotocol/interfaces/contracts/contracts/discovery/IServiceRegistry.sol"; +/** + * @title Service Registry Storage V1 + * @author Edge & Node + * @notice Storage contract for the Service Registry + */ contract ServiceRegistryV1Storage is Managed { // -- State -- + /// @notice Mapping of indexer addresses to their service information mapping(address => IServiceRegistry.IndexerService) public services; } diff --git a/packages/contracts/contracts/discovery/SubgraphNFT.sol b/packages/contracts/contracts/discovery/SubgraphNFT.sol index 3c514718c..22fc307c0 100644 --- a/packages/contracts/contracts/discovery/SubgraphNFT.sol +++ b/packages/contracts/contracts/discovery/SubgraphNFT.sol @@ -2,35 +2,66 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -import "../governance/Governed.sol"; -import "../libraries/HexStrings.sol"; -import "./ISubgraphNFT.sol"; -import "./ISubgraphNFTDescriptor.sol"; - -/// @title NFT that represents ownership of a Subgraph +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-calldata-parameters, gas-indexed-events, gas-small-strings +// solhint-disable named-parameters-mapping + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + +import { Governed } from "../governance/Governed.sol"; +import { HexStrings } from "../libraries/HexStrings.sol"; +import { ISubgraphNFT } from "@graphprotocol/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol"; +import { ISubgraphNFTDescriptor } from "@graphprotocol/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol"; + +/** + * @title NFT that represents ownership of a Subgraph + * @author Edge & Node + * @notice NFT that represents ownership of a Subgraph + */ contract SubgraphNFT is Governed, ERC721, ISubgraphNFT { // -- State -- + /// @notice Address of the minter contract address public minter; + /// @notice Address of the token descriptor contract ISubgraphNFTDescriptor public tokenDescriptor; + /// @dev Mapping from token ID to subgraph metadata hash mapping(uint256 => bytes32) private _subgraphMetadataHashes; // -- Events -- + /** + * @notice Emitted when the minter address is updated + * @param minter Address of the new minter + */ event MinterUpdated(address minter); + + /** + * @notice Emitted when the token descriptor is updated + * @param tokenDescriptor Address of the new token descriptor + */ event TokenDescriptorUpdated(address tokenDescriptor); + + /** + * @notice Emitted when subgraph metadata is updated + * @param tokenID ID of the token + * @param subgraphURI IPFS hash of the subgraph metadata + */ event SubgraphMetadataUpdated(uint256 indexed tokenID, bytes32 subgraphURI); // -- Modifiers -- + /// @dev Modifier to restrict access to minter only modifier onlyMinter() { require(msg.sender == minter, "Must be a minter"); _; } + /** + * @notice Constructor for the SubgraphNFT contract + * @param _governor Address that will have governance privileges + */ constructor(address _governor) ERC721("Subgraph", "SG") { _initialize(_governor); } @@ -38,9 +69,7 @@ contract SubgraphNFT is Governed, ERC721, ISubgraphNFT { // -- Config -- /** - * @notice Set the minter allowed to perform actions on the NFT. - * @dev Minter can mint, burn and update the metadata - * @param _minter Address of the allowed minter + * @inheritdoc ISubgraphNFT */ function setMinter(address _minter) external override onlyGovernor { _setMinter(_minter); @@ -57,16 +86,14 @@ contract SubgraphNFT is Governed, ERC721, ISubgraphNFT { } /** - * @notice Set the token descriptor contract. - * @dev Token descriptor can be zero. If set, it must be a contract. - * @param _tokenDescriptor Address of the contract that creates the NFT token URI + * @inheritdoc ISubgraphNFT */ function setTokenDescriptor(address _tokenDescriptor) external override onlyGovernor { _setTokenDescriptor(_tokenDescriptor); } /** - * @dev Internal: Set the token descriptor contract used to create the ERC-721 metadata URI. + * @notice Internal: Set the token descriptor contract used to create the ERC-721 metadata URI. * @param _tokenDescriptor Address of the contract that creates the NFT token URI */ function _setTokenDescriptor(address _tokenDescriptor) internal { @@ -79,9 +106,7 @@ contract SubgraphNFT is Governed, ERC721, ISubgraphNFT { } /** - * @notice Set the base URI. - * @dev Can be set to empty. - * @param _baseURI Base URI to use to build the token URI + * @inheritdoc ISubgraphNFT */ function setBaseURI(string memory _baseURI) external override onlyGovernor { _setBaseURI(_baseURI); @@ -90,29 +115,21 @@ contract SubgraphNFT is Governed, ERC721, ISubgraphNFT { // -- Minter actions -- /** - * @notice Mint `_tokenId` and transfers it to `_to`. - * @dev `tokenId` must not exist and `to` cannot be the zero address. - * @param _to Address receiving the minted NFT - * @param _tokenId ID of the NFT + * @inheritdoc ISubgraphNFT */ function mint(address _to, uint256 _tokenId) external override onlyMinter { _mint(_to, _tokenId); } /** - * @notice Burn `_tokenId`. - * @dev The approval is cleared when the token is burned. - * @param _tokenId ID of the NFT + * @inheritdoc ISubgraphNFT */ function burn(uint256 _tokenId) external override onlyMinter { _burn(_tokenId); } /** - * @notice Set the metadata for a subgraph represented by `_tokenId`. - * @dev `_tokenId` must exist. - * @param _tokenId ID of the NFT - * @param _subgraphMetadata IPFS hash for the metadata + * @inheritdoc ISubgraphNFT */ function setSubgraphMetadata(uint256 _tokenId, bytes32 _subgraphMetadata) external override onlyMinter { require(_exists(_tokenId), "ERC721Metadata: URI set of nonexistent token"); diff --git a/packages/contracts/contracts/discovery/SubgraphNFTDescriptor.sol b/packages/contracts/contracts/discovery/SubgraphNFTDescriptor.sol index 81f6da696..a4f5a5080 100644 --- a/packages/contracts/contracts/discovery/SubgraphNFTDescriptor.sol +++ b/packages/contracts/contracts/discovery/SubgraphNFTDescriptor.sol @@ -2,10 +2,14 @@ pragma solidity ^0.7.6; -import "../libraries/Base58Encoder.sol"; -import "./ISubgraphNFTDescriptor.sol"; +import { Base58Encoder } from "../libraries/Base58Encoder.sol"; +import { ISubgraphNFTDescriptor } from "@graphprotocol/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol"; -/// @title Describes subgraph NFT tokens via URI +/** + * @title Describes subgraph NFT tokens via URI + * @author Edge & Node + * @notice Describes subgraph NFT tokens via URI + */ contract SubgraphNFTDescriptor is ISubgraphNFTDescriptor { /// @inheritdoc ISubgraphNFTDescriptor function tokenURI( diff --git a/packages/contracts/contracts/discovery/erc1056/EthereumDIDRegistry.sol b/packages/contracts/contracts/discovery/erc1056/EthereumDIDRegistry.sol index e8545dd4a..76c1a41f9 100644 --- a/packages/contracts/contracts/discovery/erc1056/EthereumDIDRegistry.sol +++ b/packages/contracts/contracts/discovery/erc1056/EthereumDIDRegistry.sol @@ -12,19 +12,51 @@ As well as all testnets pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, gas-indexed-events, gas-small-strings +// solhint-disable named-parameters-mapping + +/** + * @title Ethereum DID Registry + * @author Edge & Node + * @notice Registry for Ethereum Decentralized Identifiers (DIDs) + */ contract EthereumDIDRegistry { + /// @notice Mapping of identity addresses to their owners mapping(address => address) public owners; + /// @notice Mapping of identity addresses to delegate types to delegate addresses to validity periods mapping(address => mapping(bytes32 => mapping(address => uint256))) public delegates; + /// @notice Mapping of identity addresses to their last change block numbers mapping(address => uint256) public changed; + /// @notice Mapping of identity addresses to their nonce values mapping(address => uint256) public nonce; + /** + * @notice Modifier to restrict access to identity owners only + * @param identity The identity address + * @param actor The address performing the action + */ modifier onlyOwner(address identity, address actor) { require(actor == identityOwner(identity), "Caller must be the identity owner"); _; } + /** + * @notice Emitted when a DID owner is changed + * @param identity The identity address + * @param owner The new owner address + * @param previousChange Block number of the previous change + */ event DIDOwnerChanged(address indexed identity, address owner, uint256 previousChange); + /** + * @notice Emitted when a DID delegate is changed + * @param identity The identity address + * @param delegateType The type of delegate + * @param delegate The delegate address + * @param validTo Timestamp until which the delegate is valid + * @param previousChange Block number of the previous change + */ event DIDDelegateChanged( address indexed identity, bytes32 delegateType, @@ -33,6 +65,14 @@ contract EthereumDIDRegistry { uint256 previousChange ); + /** + * @notice Emitted when a DID attribute is changed + * @param identity The identity address + * @param name The attribute name + * @param value The attribute value + * @param validTo Timestamp until which the attribute is valid + * @param previousChange Block number of the previous change + */ event DIDAttributeChanged( address indexed identity, bytes32 name, @@ -41,6 +81,11 @@ contract EthereumDIDRegistry { uint256 previousChange ); + /** + * @notice Get the owner of an identity + * @param identity The identity address + * @return The address of the identity owner + */ function identityOwner(address identity) public view returns (address) { address owner = owners[identity]; if (owner != address(0)) { @@ -49,6 +94,15 @@ contract EthereumDIDRegistry { return identity; } + /** + * @notice Verify signature and return signer address + * @param identity The identity address + * @param sigV Recovery ID of the signature + * @param sigR R component of the signature + * @param sigS S component of the signature + * @param hash Hash that was signed + * @return The address of the signer + */ function checkSignature( address identity, uint8 sigV, @@ -62,22 +116,48 @@ contract EthereumDIDRegistry { return signer; } + /** + * @notice Check if a delegate is valid for an identity + * @param identity The identity address + * @param delegateType The type of delegate + * @param delegate The delegate address + * @return True if the delegate is valid, false otherwise + */ function validDelegate(address identity, bytes32 delegateType, address delegate) public view returns (bool) { uint256 validity = delegates[identity][keccak256(abi.encode(delegateType))][delegate]; /* solium-disable-next-line security/no-block-members*/ return (validity > block.timestamp); } + /** + * @notice Internal function to change the owner of an identity + * @param identity The identity address + * @param actor The address performing the action + * @param newOwner The new owner address + */ function changeOwner(address identity, address actor, address newOwner) internal onlyOwner(identity, actor) { owners[identity] = newOwner; emit DIDOwnerChanged(identity, newOwner, changed[identity]); changed[identity] = block.number; } + /** + * @notice Change the owner of an identity + * @param identity The identity address + * @param newOwner The new owner address + */ function changeOwner(address identity, address newOwner) public { changeOwner(identity, msg.sender, newOwner); } + /** + * @notice Change the owner of an identity using a signed message + * @param identity The identity address + * @param sigV Recovery ID of the signature + * @param sigR R component of the signature + * @param sigS S component of the signature + * @param newOwner The new owner address + */ function changeOwnerSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, address newOwner) public { bytes32 hash = keccak256( abi.encodePacked( @@ -93,6 +173,14 @@ contract EthereumDIDRegistry { changeOwner(identity, checkSignature(identity, sigV, sigR, sigS, hash), newOwner); } + /** + * @notice Internal function to add a delegate for an identity + * @param identity The identity address + * @param actor The address performing the action + * @param delegateType The type of delegate + * @param delegate The delegate address + * @param validity The validity period in seconds + */ function addDelegate( address identity, address actor, @@ -113,10 +201,27 @@ contract EthereumDIDRegistry { changed[identity] = block.number; } + /** + * @notice Add a delegate for an identity + * @param identity The identity to add a delegate for + * @param delegateType The type of delegate + * @param delegate The address of the delegate + * @param validity The validity period in seconds + */ function addDelegate(address identity, bytes32 delegateType, address delegate, uint256 validity) public { addDelegate(identity, msg.sender, delegateType, delegate, validity); } + /** + * @notice Add a delegate for an identity using a signed message + * @param identity The identity to add a delegate for + * @param sigV The recovery id of the signature + * @param sigR The r component of the signature + * @param sigS The s component of the signature + * @param delegateType The type of delegate + * @param delegate The address of the delegate + * @param validity The validity period in seconds + */ function addDelegateSigned( address identity, uint8 sigV, @@ -142,6 +247,13 @@ contract EthereumDIDRegistry { addDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate, validity); } + /** + * @notice Internal function to revoke a delegate for an identity + * @param identity The identity address + * @param actor The address performing the action + * @param delegateType The type of delegate + * @param delegate The delegate address + */ function revokeDelegate( address identity, address actor, @@ -155,10 +267,25 @@ contract EthereumDIDRegistry { changed[identity] = block.number; } + /** + * @notice Revoke a delegate for an identity + * @param identity The identity to revoke a delegate for + * @param delegateType The type of delegate + * @param delegate The address of the delegate + */ function revokeDelegate(address identity, bytes32 delegateType, address delegate) public { revokeDelegate(identity, msg.sender, delegateType, delegate); } + /** + * @notice Revoke a delegate for an identity using a signed message + * @param identity The identity to revoke a delegate for + * @param sigV The recovery id of the signature + * @param sigR The r component of the signature + * @param sigS The s component of the signature + * @param delegateType The type of delegate + * @param delegate The address of the delegate + */ function revokeDelegateSigned( address identity, uint8 sigV, @@ -182,6 +309,14 @@ contract EthereumDIDRegistry { revokeDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate); } + /** + * @notice Internal function to set an attribute for an identity + * @param identity The identity address + * @param actor The address performing the action + * @param name The attribute name + * @param value The attribute value + * @param validity The validity period in seconds + */ function setAttribute( address identity, address actor, @@ -194,10 +329,27 @@ contract EthereumDIDRegistry { changed[identity] = block.number; } + /** + * @notice Set an attribute for an identity + * @param identity The identity to set an attribute for + * @param name The name of the attribute + * @param value The value of the attribute + * @param validity The validity period in seconds + */ function setAttribute(address identity, bytes32 name, bytes memory value, uint256 validity) public { setAttribute(identity, msg.sender, name, value, validity); } + /** + * @notice Set an attribute for an identity using a signed message + * @param identity The identity to set an attribute for + * @param sigV The recovery id of the signature + * @param sigR The r component of the signature + * @param sigS The s component of the signature + * @param name The name of the attribute + * @param value The value of the attribute + * @param validity The validity period in seconds + */ function setAttributeSigned( address identity, uint8 sigV, @@ -223,6 +375,13 @@ contract EthereumDIDRegistry { setAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value, validity); } + /** + * @notice Internal function to revoke an attribute for an identity + * @param identity The identity address + * @param actor The address performing the action + * @param name The attribute name + * @param value The attribute value + */ function revokeAttribute( address identity, address actor, @@ -233,10 +392,25 @@ contract EthereumDIDRegistry { changed[identity] = block.number; } + /** + * @notice Revoke an attribute for an identity + * @param identity The identity to revoke an attribute for + * @param name The name of the attribute + * @param value The value of the attribute + */ function revokeAttribute(address identity, bytes32 name, bytes memory value) public { revokeAttribute(identity, msg.sender, name, value); } + /** + * @notice Revoke an attribute for an identity using a signed message + * @param identity The identity to revoke an attribute for + * @param sigV The recovery id of the signature + * @param sigR The r component of the signature + * @param sigS The s component of the signature + * @param name The name of the attribute + * @param value The value of the attribute + */ function revokeAttributeSigned( address identity, uint8 sigV, diff --git a/packages/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol b/packages/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol deleted file mode 100644 index 8de69f304..000000000 --- a/packages/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.7.6; - -interface IEthereumDIDRegistry { - function identityOwner(address identity) external view returns (address); - - function setAttribute(address identity, bytes32 name, bytes calldata value, uint256 validity) external; -} diff --git a/packages/contracts/contracts/disputes/DisputeManager.sol b/packages/contracts/contracts/disputes/DisputeManager.sol index 013a21b03..9ef426453 100644 --- a/packages/contracts/contracts/disputes/DisputeManager.sol +++ b/packages/contracts/contracts/disputes/DisputeManager.sol @@ -3,18 +3,23 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/cryptography/ECDSA.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-small-strings, gas-strict-inequalities -import "../governance/Managed.sol"; -import "../upgrades/GraphUpgradeable.sol"; -import "../utils/TokenUtils.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol"; -import "./DisputeManagerStorage.sol"; -import "./IDisputeManager.sol"; +import { Managed } from "../governance/Managed.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { TokenUtils } from "../utils/TokenUtils.sol"; +import { IStaking } from "@graphprotocol/interfaces/contracts/contracts/staking/IStaking.sol"; -/* +import { DisputeManagerV1Storage } from "./DisputeManagerStorage.sol"; +import { IDisputeManager } from "@graphprotocol/interfaces/contracts/contracts/disputes/IDisputeManager.sol"; + +/** * @title DisputeManager + * @author Edge & Node * @notice Provides a way to align the incentives of participants by having slashing as deterrent * for incorrect behaviour. * @@ -41,39 +46,61 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa // -- EIP-712 -- + /// @dev EIP-712 domain type hash for signature verification bytes32 private constant DOMAIN_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + /// @dev EIP-712 domain name hash bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Protocol"); + /// @dev EIP-712 domain version hash bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); + /// @dev EIP-712 domain salt for uniqueness bytes32 private constant DOMAIN_SALT = 0xa070ffb1cd7409649bf77822cce74495468e06dbfaef09556838bf188679b9c2; + /// @dev EIP-712 receipt type hash for attestation verification bytes32 private constant RECEIPT_TYPE_HASH = keccak256("Receipt(bytes32 requestCID,bytes32 responseCID,bytes32 subgraphDeploymentID)"); // -- Constants -- - // Attestation size is the sum of the receipt (96) + signature (65) + /// @dev Total size of attestation in bytes (receipt + signature) uint256 private constant ATTESTATION_SIZE_BYTES = RECEIPT_SIZE_BYTES + SIG_SIZE_BYTES; + /// @dev Size of receipt in bytes uint256 private constant RECEIPT_SIZE_BYTES = 96; + /// @dev Length of signature R component in bytes uint256 private constant SIG_R_LENGTH = 32; + /// @dev Length of signature S component in bytes uint256 private constant SIG_S_LENGTH = 32; + /// @dev Length of signature V component in bytes uint256 private constant SIG_V_LENGTH = 1; + /// @dev Offset of signature R component in attestation data uint256 private constant SIG_R_OFFSET = RECEIPT_SIZE_BYTES; + /// @dev Offset of signature S component in attestation data uint256 private constant SIG_S_OFFSET = RECEIPT_SIZE_BYTES + SIG_R_LENGTH; + /// @dev Offset of signature V component in attestation data uint256 private constant SIG_V_OFFSET = RECEIPT_SIZE_BYTES + SIG_R_LENGTH + SIG_S_LENGTH; + /// @dev Total size of signature in bytes uint256 private constant SIG_SIZE_BYTES = SIG_R_LENGTH + SIG_S_LENGTH + SIG_V_LENGTH; + /// @dev Length of uint8 type in bytes uint256 private constant UINT8_BYTE_LENGTH = 1; + /// @dev Length of bytes32 type in bytes uint256 private constant BYTES32_BYTE_LENGTH = 32; + /// @dev Maximum percentage in parts per million (100%) uint256 private constant MAX_PPM = 1000000; // 100% in parts per million // -- Events -- /** - * @dev Emitted when a query dispute is created for `subgraphDeploymentID` and `indexer` + * @notice Emitted when a query dispute is created for `subgraphDeploymentID` and `indexer` * by `fisherman`. * The event emits the amount of `tokens` deposited by the fisherman and `attestation` submitted. + * @param disputeID ID of the dispute + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the fisherman creating the dispute + * @param tokens Amount of tokens deposited by the fisherman + * @param subgraphDeploymentID Subgraph deployment ID being disputed + * @param attestation Attestation data submitted */ event QueryDisputeCreated( bytes32 indexed disputeID, @@ -85,9 +112,14 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa ); /** - * @dev Emitted when an indexing dispute is created for `allocationID` and `indexer` + * @notice Emitted when an indexing dispute is created for `allocationID` and `indexer` * by `fisherman`. * The event emits the amount of `tokens` deposited by the fisherman. + * @param disputeID ID of the dispute + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the fisherman creating the dispute + * @param tokens Amount of tokens deposited by the fisherman + * @param allocationID Allocation ID being disputed */ event IndexingDisputeCreated( bytes32 indexed disputeID, @@ -98,8 +130,12 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa ); /** - * @dev Emitted when arbitrator accepts a `disputeID` to `indexer` created by `fisherman`. + * @notice Emitted when arbitrator accepts a `disputeID` to `indexer` created by `fisherman`. * The event emits the amount `tokens` transferred to the fisherman, the deposit plus reward. + * @param disputeID ID of the dispute + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the fisherman who created the dispute + * @param tokens Amount of tokens transferred to the fisherman (deposit plus reward) */ event DisputeAccepted( bytes32 indexed disputeID, @@ -109,8 +145,12 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa ); /** - * @dev Emitted when arbitrator rejects a `disputeID` for `indexer` created by `fisherman`. + * @notice Emitted when arbitrator rejects a `disputeID` for `indexer` created by `fisherman`. * The event emits the amount `tokens` burned from the fisherman deposit. + * @param disputeID ID of the dispute + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the fisherman who created the dispute + * @param tokens Amount of tokens burned from the fisherman deposit */ event DisputeRejected( bytes32 indexed disputeID, @@ -120,20 +160,29 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa ); /** - * @dev Emitted when arbitrator draw a `disputeID` for `indexer` created by `fisherman`. + * @notice Emitted when arbitrator draw a `disputeID` for `indexer` created by `fisherman`. * The event emits the amount `tokens` used as deposit and returned to the fisherman. + * @param disputeID ID of the dispute + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the fisherman who created the dispute + * @param tokens Amount of tokens used as deposit and returned to the fisherman */ event DisputeDrawn(bytes32 indexed disputeID, address indexed indexer, address indexed fisherman, uint256 tokens); /** - * @dev Emitted when two disputes are in conflict to link them. + * @notice Emitted when two disputes are in conflict to link them. * This event will be emitted after each DisputeCreated event is emitted * for each of the individual disputes. + * @param disputeID1 ID of the first dispute + * @param disputeID2 ID of the second dispute */ event DisputeLinked(bytes32 indexed disputeID1, bytes32 indexed disputeID2); // -- Modifiers -- + /** + * @notice Internal function to check if the caller is the arbitrator + */ function _onlyArbitrator() internal view { require(msg.sender == arbitrator, "Caller is not the Arbitrator"); } @@ -146,6 +195,10 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa _; } + /** + * @dev Check if the dispute exists and is pending + * @param _disputeID ID of the dispute to check + */ modifier onlyPendingDispute(bytes32 _disputeID) { require(isDisputeCreated(_disputeID), "Dispute does not exist"); require(disputes[_disputeID].status == IDisputeManager.DisputeStatus.Pending, "Dispute must be pending"); @@ -155,7 +208,8 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa // -- Functions -- /** - * @dev Initialize this contract. + * @notice Initialize this contract. + * @param _controller Controller address * @param _arbitrator Arbitrator role * @param _minimumDeposit Minimum deposit required to create a Dispute * @param _fishermanRewardPercentage Percent of slashed funds for fisherman (ppm) @@ -192,16 +246,13 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Set the arbitrator address. - * @notice Update the arbitrator to `_arbitrator` - * @param _arbitrator The address of the arbitration contract or party + * @inheritdoc IDisputeManager */ function setArbitrator(address _arbitrator) external override onlyGovernor { _setArbitrator(_arbitrator); } /** - * @dev Internal: Set the arbitrator address. * @notice Update the arbitrator to `_arbitrator` * @param _arbitrator The address of the arbitration contract or party */ @@ -212,16 +263,13 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Set the minimum deposit required to create a dispute. - * @notice Update the minimum deposit to `_minimumDeposit` Graph Tokens - * @param _minimumDeposit The minimum deposit in Graph Tokens + * @inheritdoc IDisputeManager */ function setMinimumDeposit(uint256 _minimumDeposit) external override onlyGovernor { _setMinimumDeposit(_minimumDeposit); } /** - * @dev Internal: Set the minimum deposit required to create a dispute. * @notice Update the minimum deposit to `_minimumDeposit` Graph Tokens * @param _minimumDeposit The minimum deposit in Graph Tokens */ @@ -232,17 +280,14 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Set the percent reward that the fisherman gets when slashing occurs. - * @notice Update the reward percentage to `_percentage` - * @param _percentage Reward as a percentage of indexer stake + * @inheritdoc IDisputeManager */ function setFishermanRewardPercentage(uint32 _percentage) external override onlyGovernor { _setFishermanRewardPercentage(_percentage); } /** - * @dev Internal: Set the percent reward that the fisherman gets when slashing occurs. - * @notice Update the reward percentage to `_percentage` + * @notice Set the percent reward that the fisherman gets when slashing occurs. * @param _percentage Reward as a percentage of indexer stake */ function _setFishermanRewardPercentage(uint32 _percentage) private { @@ -253,16 +298,14 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Set the percentage used for slashing indexers. - * @param _qryPercentage Percentage slashing for query disputes - * @param _idxPercentage Percentage slashing for indexing disputes + * @inheritdoc IDisputeManager */ function setSlashingPercentage(uint32 _qryPercentage, uint32 _idxPercentage) external override onlyGovernor { _setSlashingPercentage(_qryPercentage, _idxPercentage); } /** - * @dev Internal: Set the percentage used for slashing indexers. + * @notice Internal: Set the percentage used for slashing indexers. * @param _qryPercentage Percentage slashing for query disputes * @param _idxPercentage Percentage slashing for indexing disputes */ @@ -279,21 +322,16 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Return whether a dispute exists or not. - * @notice Return if dispute with ID `_disputeID` exists - * @param _disputeID True if dispute already exists + * @inheritdoc IDisputeManager */ function isDisputeCreated(bytes32 _disputeID) public view override returns (bool) { return disputes[_disputeID].status != DisputeStatus.Null; } /** - * @dev Get the message hash that an indexer used to sign the receipt. - * Encodes a receipt using a domain separator, as described on + * @inheritdoc IDisputeManager + * @dev Encodes a receipt using a domain separator, as described on * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification. - * @notice Return the message hash used to sign the receipt - * @param _receipt Receipt returned by indexer and submitted by fisherman - * @return Message hash used to sign the receipt */ function encodeHashReceipt(Receipt memory _receipt) public view override returns (bytes32) { return @@ -314,11 +352,8 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Returns if two attestations are conflicting. - * Everything must match except for the responseID. - * @param _attestation1 Attestation - * @param _attestation2 Attestation - * @return True if the two attestations are conflicting + * @inheritdoc IDisputeManager + * @dev Everything must match except for the responseID. */ function areConflictingAttestations( Attestation memory _attestation1, @@ -330,9 +365,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Returns the indexer that signed an attestation. - * @param _attestation Attestation - * @return Indexer address + * @inheritdoc IDisputeManager */ function getAttestationIndexer(Attestation memory _attestation) public view override returns (address) { // Get attestation signer. Indexers signs with the allocationID @@ -348,11 +381,9 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Create a query dispute for the arbitrator to resolve. - * This function is called by a fisherman that will need to `_deposit` at + * @inheritdoc IDisputeManager + * @dev This function is called by a fisherman that will need to `_deposit` at * least `minimumDeposit` GRT tokens. - * @param _attestationData Attestation bytes submitted by the fisherman - * @param _deposit Amount of tokens staked as deposit */ function createQueryDispute(bytes calldata _attestationData, uint256 _deposit) external override returns (bytes32) { // Get funds from submitter @@ -369,16 +400,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Create query disputes for two conflicting attestations. - * A conflicting attestation is a proof presented by two different indexers - * where for the same request on a subgraph the response is different. - * For this type of dispute the submitter is not required to present a deposit - * as one of the attestation is considered to be right. - * Two linked disputes will be created and if the arbitrator resolve one, the other - * one will be automatically resolved. - * @param _attestationData1 First attestation data submitted - * @param _attestationData2 Second attestation data submitted - * @return DisputeID1, DisputeID2 + * @inheritdoc IDisputeManager */ function createQueryDisputeConflict( bytes calldata _attestationData1, @@ -409,7 +431,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Create a query dispute passing the parsed attestation. + * @notice Create a query dispute passing the parsed attestation. * To be used in createQueryDispute() and createQueryDisputeConflict() * to avoid calling parseAttestation() multiple times * `_attestationData` is only passed to be emitted @@ -472,8 +494,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa * The disputes are created in reference to an allocationID * This function is called by a challenger that will need to `_deposit` at * least `minimumDeposit` GRT tokens. - * @param _allocationID The allocation to dispute - * @param _deposit Amount of tokens staked as deposit + * @inheritdoc IDisputeManager */ function createIndexingDispute(address _allocationID, uint256 _deposit) external override returns (bytes32) { // Get funds from submitter @@ -484,12 +505,12 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Create indexing dispute internal function. + * @notice Create indexing dispute internal function. * @param _fisherman The challenger creating the dispute * @param _deposit Amount of tokens staked as deposit * @param _allocationID Allocation disputed + * @return disputeID The ID of the created dispute */ - function _createIndexingDisputeWithAllocation( address _fisherman, uint256 _deposit, @@ -525,12 +546,10 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev The arbitrator accepts a dispute as being valid. - * This function will revert if the indexer is not slashable, whether because it does not have + * @dev This function will revert if the indexer is not slashable, whether because it does not have * any stake available or the slashing percentage is configured to be zero. In those cases * a dispute must be resolved using drawDispute or rejectDispute. - * @notice Accept a dispute with ID `_disputeID` - * @param _disputeID ID of the dispute to be accepted + * @inheritdoc IDisputeManager */ function acceptDispute(bytes32 _disputeID) external override onlyArbitrator onlyPendingDispute(_disputeID) { Dispute storage dispute = disputes[_disputeID]; @@ -552,9 +571,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev The arbitrator rejects a dispute as being invalid. - * @notice Reject a dispute with ID `_disputeID` - * @param _disputeID ID of the dispute to be rejected + * @inheritdoc IDisputeManager */ function rejectDispute(bytes32 _disputeID) public override onlyArbitrator onlyPendingDispute(_disputeID) { Dispute storage dispute = disputes[_disputeID]; @@ -575,9 +592,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev The arbitrator draws dispute. - * @notice Ignore a dispute with ID `_disputeID` - * @param _disputeID ID of the dispute to be disregarded + * @inheritdoc IDisputeManager */ function drawDispute(bytes32 _disputeID) external override onlyArbitrator onlyPendingDispute(_disputeID) { Dispute storage dispute = disputes[_disputeID]; @@ -595,7 +610,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Returns whether the dispute is for a conflicting attestation or not. + * @notice Returns whether the dispute is for a conflicting attestation or not. * @param _dispute Dispute * @return True conflicting attestation dispute */ @@ -606,7 +621,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Resolve the conflicting dispute if there is any for the one passed to this function. + * @notice Resolve the conflicting dispute if there is any for the one passed to this function. * @param _dispute Dispute * @return True if resolved */ @@ -621,7 +636,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Pull deposit from submitter account. + * @notice Pull deposit from submitter account. * @param _deposit Amount of tokens to deposit */ function _pullSubmitterDeposit(uint256 _deposit) private { @@ -633,7 +648,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Make the staking contract slash the indexer and reward the challenger. + * @notice Make the staking contract slash the indexer and reward the challenger. * Give the challenger a reward equal to the fishermanRewardPercentage of slashed amount * @param _indexer Address of the indexer * @param _challenger Address of the challenger @@ -664,7 +679,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Return the slashing percentage for the dispute type. + * @notice Return the slashing percentage for the dispute type. * @param _disputeType Dispute type * @return Slashing percentage to use for the dispute type */ @@ -675,7 +690,7 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Recover the signer address of the `_attestation`. + * @notice Recover the signer address of the `_attestation`. * @param _attestation The attestation struct * @return Signer address */ @@ -694,11 +709,12 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Get the running network chain ID + * @notice Get the running network chain ID * @return The chain ID */ function _getChainID() private pure returns (uint256) { uint256 id; + // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } @@ -706,7 +722,8 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Parse the bytes attestation into a struct from `_data`. + * @notice Parse the bytes attestation into a struct from `_data`. + * @param _data The bytes data to parse into an attestation * @return Attestation struct */ function _parseAttestation(bytes memory _data) private pure returns (Attestation memory) { @@ -729,13 +746,16 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Parse a uint8 from `_bytes` starting at offset `_start`. + * @notice Parse a uint8 from `_bytes` starting at offset `_start`. + * @param _bytes The bytes array to parse from + * @param _start The starting offset in the bytes array * @return uint8 value */ function _toUint8(bytes memory _bytes, uint256 _start) private pure returns (uint8) { require(_bytes.length >= (_start + UINT8_BYTE_LENGTH), "Bytes: out of bounds"); uint8 tempUint; + // solhint-disable-next-line no-inline-assembly assembly { tempUint := mload(add(add(_bytes, 0x1), _start)) } @@ -744,13 +764,16 @@ contract DisputeManager is DisputeManagerV1Storage, GraphUpgradeable, IDisputeMa } /** - * @dev Parse a bytes32 from `_bytes` starting at offset `_start`. + * @notice Parse a bytes32 from `_bytes` starting at offset `_start`. + * @param _bytes The bytes array to parse from + * @param _start The starting offset in the bytes array * @return bytes32 value */ function _toBytes32(bytes memory _bytes, uint256 _start) private pure returns (bytes32) { require(_bytes.length >= (_start + BYTES32_BYTE_LENGTH), "Bytes: out of bounds"); bytes32 tempBytes32; + // solhint-disable-next-line no-inline-assembly assembly { tempBytes32 := mload(add(add(_bytes, 0x20), _start)) } diff --git a/packages/contracts/contracts/disputes/DisputeManagerStorage.sol b/packages/contracts/contracts/disputes/DisputeManagerStorage.sol index 4df6e0ae6..f2ad2b7c3 100644 --- a/packages/contracts/contracts/disputes/DisputeManagerStorage.sol +++ b/packages/contracts/contracts/disputes/DisputeManagerStorage.sol @@ -1,34 +1,44 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6; -import "../governance/Managed.sol"; +import { Managed } from "../governance/Managed.sol"; -import "./IDisputeManager.sol"; +import { IDisputeManager } from "@graphprotocol/interfaces/contracts/contracts/disputes/IDisputeManager.sol"; +/** + * @title Dispute Manager Storage V1 + * @author Edge & Node + * @notice Storage contract for the Dispute Manager + */ contract DisputeManagerV1Storage is Managed { // -- State -- - bytes32 internal DOMAIN_SEPARATOR; + /// @dev Domain separator for EIP-712 signature verification + bytes32 internal DOMAIN_SEPARATOR; // solhint-disable-line var-name-mixedcase - // The arbitrator is solely in control of arbitrating disputes + /// @notice The arbitrator is solely in control of arbitrating disputes address public arbitrator; - // Minimum deposit required to create a Dispute + /// @notice Minimum deposit required to create a Dispute uint256 public minimumDeposit; // -- Slot 0xf - // Percentage of indexer slashed funds to assign as a reward to fisherman in successful dispute - // Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + /// @notice Percentage of indexer slashed funds to assign as a reward to fisherman in successful dispute + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) uint32 public fishermanRewardPercentage; - // Percentage of indexer stake to slash on disputes - // Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) + /// @notice Percentage of indexer stake to slash on disputes + /// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%) uint32 public qrySlashingPercentage; + /// @notice Percentage of indexer stake to slash on disputes uint32 public idxSlashingPercentage; // -- Slot 0x10 - // Disputes created : disputeID => Dispute - // disputeID - check creation functions to see how disputeID is built + /// @notice Disputes created : disputeID => Dispute + /// @dev disputeID - check creation functions to see how disputeID is built mapping(bytes32 => IDisputeManager.Dispute) public disputes; } diff --git a/packages/contracts/contracts/disputes/IDisputeManager.sol b/packages/contracts/contracts/disputes/IDisputeManager.sol deleted file mode 100644 index e42386941..000000000 --- a/packages/contracts/contracts/disputes/IDisputeManager.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; -pragma abicoder v2; - -interface IDisputeManager { - // -- Dispute -- - - enum DisputeType { - Null, - IndexingDispute, - QueryDispute - } - - enum DisputeStatus { - Null, - Accepted, - Rejected, - Drawn, - Pending - } - - // Disputes contain info necessary for the Arbitrator to verify and resolve - struct Dispute { - address indexer; - address fisherman; - uint256 deposit; - bytes32 relatedDisputeID; - DisputeType disputeType; - DisputeStatus status; - } - - // -- Attestation -- - - // Receipt content sent from indexer in response to request - struct Receipt { - bytes32 requestCID; - bytes32 responseCID; - bytes32 subgraphDeploymentID; - } - - // Attestation sent from indexer in response to a request - struct Attestation { - bytes32 requestCID; - bytes32 responseCID; - bytes32 subgraphDeploymentID; - bytes32 r; - bytes32 s; - uint8 v; - } - - // -- Configuration -- - - function setArbitrator(address _arbitrator) external; - - function setMinimumDeposit(uint256 _minimumDeposit) external; - - function setFishermanRewardPercentage(uint32 _percentage) external; - - function setSlashingPercentage(uint32 _qryPercentage, uint32 _idxPercentage) external; - - // -- Getters -- - - function isDisputeCreated(bytes32 _disputeID) external view returns (bool); - - function encodeHashReceipt(Receipt memory _receipt) external view returns (bytes32); - - function areConflictingAttestations( - Attestation memory _attestation1, - Attestation memory _attestation2 - ) external pure returns (bool); - - function getAttestationIndexer(Attestation memory _attestation) external view returns (address); - - // -- Dispute -- - - function createQueryDispute(bytes calldata _attestationData, uint256 _deposit) external returns (bytes32); - - function createQueryDisputeConflict( - bytes calldata _attestationData1, - bytes calldata _attestationData2 - ) external returns (bytes32, bytes32); - - function createIndexingDispute(address _allocationID, uint256 _deposit) external returns (bytes32); - - function acceptDispute(bytes32 _disputeID) external; - - function rejectDispute(bytes32 _disputeID) external; - - function drawDispute(bytes32 _disputeID) external; -} diff --git a/packages/contracts/contracts/epochs/EpochManager.sol b/packages/contracts/contracts/epochs/EpochManager.sol index 281b63896..d69002794 100644 --- a/packages/contracts/contracts/epochs/EpochManager.sol +++ b/packages/contracts/contracts/epochs/EpochManager.sol @@ -2,27 +2,45 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts/math/SafeMath.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-small-strings, gas-strict-inequalities -import "../upgrades/GraphUpgradeable.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import "./EpochManagerStorage.sol"; -import "./IEpochManager.sol"; +import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; +import { Managed } from "../governance/Managed.sol"; + +import { EpochManagerV1Storage } from "./EpochManagerStorage.sol"; +import { IEpochManager } from "@graphprotocol/interfaces/contracts/contracts/epochs/IEpochManager.sol"; /** * @title EpochManager contract - * @dev Produce epochs based on a number of blocks to coordinate contracts in the protocol. + * @author Edge & Node + * @notice Produce epochs based on a number of blocks to coordinate contracts in the protocol. */ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager { using SafeMath for uint256; // -- Events -- + /** + * @notice Emitted when an epoch is run + * @param epoch The epoch number that was run + * @param caller Address that called runEpoch() + */ event EpochRun(uint256 indexed epoch, address caller); + + /** + * @notice Emitted when the epoch length is updated + * @param epoch The epoch when the length was updated + * @param epochLength The new epoch length in blocks + */ event EpochLengthUpdate(uint256 indexed epoch, uint256 epochLength); /** - * @dev Initialize this contract. + * @notice Initialize this contract. + * @param _controller Address of the Controller contract + * @param _epochLength Length of each epoch in blocks */ function initialize(address _controller, uint256 _epochLength) external onlyImpl { require(_epochLength > 0, "Epoch length cannot be 0"); @@ -39,9 +57,7 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager } /** - * @dev Set the epoch length. - * @notice Set epoch length to `_epochLength` blocks - * @param _epochLength Epoch length in blocks + * @inheritdoc IEpochManager */ function setEpochLength(uint256 _epochLength) external override onlyGovernor { require(_epochLength > 0, "Epoch length cannot be 0"); @@ -55,8 +71,7 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager } /** - * @dev Run a new epoch, should be called once at the start of any epoch. - * @notice Perform state changes for the current epoch + * @inheritdoc IEpochManager */ function runEpoch() external override { // Check if already called for the current epoch @@ -70,24 +85,21 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager } /** - * @dev Return true if the current epoch has already run. - * @return Return true if current epoch is the last epoch that has run + * @inheritdoc IEpochManager */ function isCurrentEpochRun() public view override returns (bool) { return lastRunEpoch == currentEpoch(); } /** - * @dev Return current block number. - * @return Block number + * @inheritdoc IEpochManager */ function blockNum() public view override returns (uint256) { return block.number; } /** - * @dev Return blockhash for a block. - * @return BlockHash for `_block` number + * @inheritdoc IEpochManager */ function blockHash(uint256 _block) external view override returns (bytes32) { uint256 currentBlock = blockNum(); @@ -99,33 +111,28 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager } /** - * @dev Return the current epoch, it may have not been run yet. - * @return The current epoch based on epoch length + * @inheritdoc IEpochManager */ function currentEpoch() public view override returns (uint256) { return lastLengthUpdateEpoch.add(epochsSinceUpdate()); } /** - * @dev Return block where the current epoch started. - * @return The block number when the current epoch started + * @inheritdoc IEpochManager */ function currentEpochBlock() public view override returns (uint256) { return lastLengthUpdateBlock.add(epochsSinceUpdate().mul(epochLength)); } /** - * @dev Return the number of blocks that passed since current epoch started. - * @return Blocks that passed since start of epoch + * @inheritdoc IEpochManager */ function currentEpochBlockSinceStart() external view override returns (uint256) { return blockNum() - currentEpochBlock(); } /** - * @dev Return the number of epoch that passed since another epoch. - * @param _epoch Epoch to use as since epoch value - * @return Number of epochs and current epoch + * @inheritdoc IEpochManager */ function epochsSince(uint256 _epoch) external view override returns (uint256) { uint256 epoch = currentEpoch(); @@ -133,8 +140,7 @@ contract EpochManager is EpochManagerV1Storage, GraphUpgradeable, IEpochManager } /** - * @dev Return number of epochs passed since last epoch length update. - * @return The number of epoch that passed since last epoch length update + * @inheritdoc IEpochManager */ function epochsSinceUpdate() public view override returns (uint256) { return blockNum().sub(lastLengthUpdateBlock).div(epochLength); diff --git a/packages/contracts/contracts/epochs/EpochManagerStorage.sol b/packages/contracts/contracts/epochs/EpochManagerStorage.sol index 5f8599434..894f34a46 100644 --- a/packages/contracts/contracts/epochs/EpochManagerStorage.sol +++ b/packages/contracts/contracts/epochs/EpochManagerStorage.sol @@ -2,18 +2,24 @@ pragma solidity ^0.7.6; -import "../governance/Managed.sol"; +import { Managed } from "../governance/Managed.sol"; +/** + * @title Epoch Manager Storage V1 + * @author Edge & Node + * @notice Storage contract for the Epoch Manager + */ contract EpochManagerV1Storage is Managed { // -- State -- - // Epoch length in blocks + /// @notice Epoch length in blocks uint256 public epochLength; - // Epoch that was last run + /// @notice Epoch that was last run uint256 public lastRunEpoch; - // Block and epoch when epoch length was last updated + /// @notice Epoch when epoch length was last updated uint256 public lastLengthUpdateEpoch; + /// @notice Block when epoch length was last updated uint256 public lastLengthUpdateBlock; } diff --git a/packages/contracts/contracts/epochs/IEpochManager.sol b/packages/contracts/contracts/epochs/IEpochManager.sol deleted file mode 100644 index c65280d59..000000000 --- a/packages/contracts/contracts/epochs/IEpochManager.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IEpochManager { - // -- Configuration -- - - function setEpochLength(uint256 _epochLength) external; - - // -- Epochs - - function runEpoch() external; - - // -- Getters -- - - function isCurrentEpochRun() external view returns (bool); - - function blockNum() external view returns (uint256); - - function blockHash(uint256 _block) external view returns (bytes32); - - function currentEpoch() external view returns (uint256); - - function currentEpochBlock() external view returns (uint256); - - function currentEpochBlockSinceStart() external view returns (uint256); - - function epochsSince(uint256 _epoch) external view returns (uint256); - - function epochsSinceUpdate() external view returns (uint256); -} diff --git a/packages/contracts/contracts/gateway/BridgeEscrow.sol b/packages/contracts/contracts/gateway/BridgeEscrow.sol index 73bc0a3d7..d3b50edc8 100644 --- a/packages/contracts/contracts/gateway/BridgeEscrow.sol +++ b/packages/contracts/contracts/gateway/BridgeEscrow.sol @@ -9,7 +9,8 @@ import { Managed } from "../governance/Managed.sol"; /** * @title Bridge Escrow - * @dev This contracts acts as a gateway for an L2 bridge (or several). It simply holds GRT and has + * @author Edge & Node + * @notice This contracts acts as a gateway for an L2 bridge (or several). It simply holds GRT and has * a set of spenders that can transfer the tokens; the L1 side of each L2 bridge has to be * approved as a spender. */ diff --git a/packages/contracts/contracts/gateway/GraphTokenGateway.sol b/packages/contracts/contracts/gateway/GraphTokenGateway.sol index fb992afc2..81edb9922 100644 --- a/packages/contracts/contracts/gateway/GraphTokenGateway.sol +++ b/packages/contracts/contracts/gateway/GraphTokenGateway.sol @@ -3,13 +3,14 @@ pragma solidity ^0.7.6; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; -import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { Pausable } from "../governance/Pausable.sol"; import { Managed } from "../governance/Managed.sol"; /** * @title L1/L2 Graph Token Gateway - * @dev This includes everything that's shared between the L1 and L2 sides of the bridge. + * @author Edge & Node + * @notice This includes everything that's shared between the L1 and L2 sides of the bridge. */ abstract contract GraphTokenGateway is GraphUpgradeable, Pausable, Managed, ITokenGateway { /// @dev Storage gap added in case we need to add state variables to this contract @@ -52,7 +53,7 @@ abstract contract GraphTokenGateway is GraphUpgradeable, Pausable, Managed, ITok } /** - * @dev Override the default pausing from Managed to allow pausing this + * @notice Override the default pausing from Managed to allow pausing this * particular contract instead of pausing from the Controller. */ function _notPaused() internal view override { @@ -60,7 +61,7 @@ abstract contract GraphTokenGateway is GraphUpgradeable, Pausable, Managed, ITok } /** - * @dev Runs state validation before unpausing, reverts if + * @notice Runs state validation before unpausing, reverts if * something is not set properly */ function _checksBeforeUnpause() internal view virtual; diff --git a/packages/contracts/contracts/gateway/ICallhookReceiver.sol b/packages/contracts/contracts/gateway/ICallhookReceiver.sol deleted file mode 100644 index 8d003cb76..000000000 --- a/packages/contracts/contracts/gateway/ICallhookReceiver.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -/** - * @title Interface for contracts that can receive callhooks through the Arbitrum GRT bridge - * @dev Any contract that can receive a callhook on L2, sent through the bridge from L1, must - * be allowlisted by the governor, but also implement this interface that contains - * the function that will actually be called by the L2GraphTokenGateway. - */ -pragma solidity ^0.7.6 || 0.8.27; - -interface ICallhookReceiver { - /** - * @notice Receive tokens with a callhook from the bridge - * @param _from Token sender in L1 - * @param _amount Amount of tokens that were transferred - * @param _data ABI-encoded callhook data - */ - function onTokenTransfer(address _from, uint256 _amount, bytes calldata _data) external; -} diff --git a/packages/contracts/contracts/gateway/L1GraphTokenGateway.sol b/packages/contracts/contracts/gateway/L1GraphTokenGateway.sol index 7fad927ad..d9216b956 100644 --- a/packages/contracts/contracts/gateway/L1GraphTokenGateway.sol +++ b/packages/contracts/contracts/gateway/L1GraphTokenGateway.sol @@ -3,22 +3,27 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-strict-inequalities +// solhint-disable named-parameters-mapping + import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { L1ArbitrumMessenger } from "../arbitrum/L1ArbitrumMessenger.sol"; -import { IBridge } from "../arbitrum/IBridge.sol"; -import { IInbox } from "../arbitrum/IInbox.sol"; -import { IOutbox } from "../arbitrum/IOutbox.sol"; -import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { IBridge } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IBridge.sol"; +import { IInbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IInbox.sol"; +import { IOutbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IOutbox.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { Managed } from "../governance/Managed.sol"; import { GraphTokenGateway } from "./GraphTokenGateway.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; /** * @title L1 Graph Token Gateway Contract - * @dev Provides the L1 side of the Ethereum-Arbitrum GRT bridge. Sends GRT to the L2 chain + * @author Edge & Node + * @notice Provides the L1 side of the Ethereum-Arbitrum GRT bridge. Sends GRT to the L2 chain * by escrowing them and sending a message to the L2 gateway, and receives tokens from L2 by * releasing them from escrow. * Based on Offchain Labs' reference implementation and Livepeer's arbitrum-lpt-bridge @@ -28,28 +33,35 @@ import { IGraphToken } from "../token/IGraphToken.sol"; contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMessenger { using SafeMathUpgradeable for uint256; - /// Address of the Graph Token contract on L2 + /// @notice Address of the Graph Token contract on L2 address public l2GRT; - /// Address of the Arbitrum Inbox + /// @notice Address of the Arbitrum Inbox address public inbox; - /// Address of the Arbitrum Gateway Router on L1 + /// @notice Address of the Arbitrum Gateway Router on L1 address public l1Router; - /// Address of the L2GraphTokenGateway on L2 that is the counterpart of this gateway + /// @notice Address of the L2GraphTokenGateway on L2 that is the counterpart of this gateway address public l2Counterpart; - /// Address of the BridgeEscrow contract that holds the GRT in the bridge + /// @notice Address of the BridgeEscrow contract that holds the GRT in the bridge address public escrow; - /// Addresses for which this mapping is true are allowed to send callhooks in outbound transfers + /// @notice Addresses for which this mapping is true are allowed to send callhooks in outbound transfers mapping(address => bool) public callhookAllowlist; - /// Total amount minted from L2 + /// @notice Total amount minted from L2 uint256 public totalMintedFromL2; - /// Accumulated allowance for tokens minted from L2 at lastL2MintAllowanceUpdateBlock + /// @notice Accumulated allowance for tokens minted from L2 at lastL2MintAllowanceUpdateBlock uint256 public accumulatedL2MintAllowanceSnapshot; - /// Block at which new L2 allowance starts accumulating + /// @notice Block at which new L2 allowance starts accumulating uint256 public lastL2MintAllowanceUpdateBlock; - /// New L2 mint allowance per block + /// @notice New L2 mint allowance per block uint256 public l2MintAllowancePerBlock; - /// Emitted when an outbound transfer is initiated, i.e. tokens are deposited from L1 to L2 + /** + * @notice Emitted when an outbound transfer is initiated, i.e. tokens are deposited from L1 to L2 + * @param l1Token Address of the L1 token being transferred + * @param from Address sending the tokens on L1 + * @param to Address receiving the tokens on L2 + * @param sequenceNumber Sequence number of the retryable ticket + * @param amount Amount of tokens transferred + */ event DepositInitiated( address l1Token, address indexed from, @@ -58,7 +70,14 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess uint256 amount ); - /// Emitted when an incoming transfer is finalized, i.e tokens are withdrawn from L2 to L1 + /** + * @notice Emitted when an incoming transfer is finalized, i.e tokens are withdrawn from L2 to L1 + * @param l1Token Address of the L1 token being transferred + * @param from Address sending the tokens on L2 + * @param to Address receiving the tokens on L1 + * @param exitNum Exit number (always 0 for this contract) + * @param amount Amount of tokens transferred + */ event WithdrawalFinalized( address l1Token, address indexed from, @@ -67,25 +86,58 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess uint256 amount ); - /// Emitted when the Arbitrum Inbox and Gateway Router addresses have been updated + /** + * @notice Emitted when the Arbitrum Inbox and Gateway Router addresses have been updated + * @param inbox Address of the Arbitrum Inbox + * @param l1Router Address of the L1 Gateway Router + */ event ArbitrumAddressesSet(address inbox, address l1Router); - /// Emitted when the L2 GRT address has been updated + + /** + * @notice Emitted when the L2 GRT address has been updated + * @param l2GRT Address of the L2 GRT contract + */ event L2TokenAddressSet(address l2GRT); - /// Emitted when the counterpart L2GraphTokenGateway address has been updated + + /** + * @notice Emitted when the counterpart L2GraphTokenGateway address has been updated + * @param l2Counterpart Address of the L2 counterpart gateway + */ event L2CounterpartAddressSet(address l2Counterpart); - /// Emitted when the escrow address has been updated + /** + * @notice Emitted when the escrow address has been updated + * @param escrow Address of the escrow contract + */ event EscrowAddressSet(address escrow); - /// Emitted when an address is added to the callhook allowlist + + /** + * @notice Emitted when an address is added to the callhook allowlist + * @param newAllowlisted Address added to the allowlist + */ event AddedToCallhookAllowlist(address newAllowlisted); - /// Emitted when an address is removed from the callhook allowlist + + /** + * @notice Emitted when an address is removed from the callhook allowlist + * @param notAllowlisted Address removed from the allowlist + */ event RemovedFromCallhookAllowlist(address notAllowlisted); - /// Emitted when the L2 mint allowance per block is updated + + /** + * @notice Emitted when the L2 mint allowance per block is updated + * @param accumulatedL2MintAllowanceSnapshot Accumulated allowance snapshot at update block + * @param l2MintAllowancePerBlock New allowance per block + * @param lastL2MintAllowanceUpdateBlock Block number when allowance was updated + */ event L2MintAllowanceUpdated( uint256 accumulatedL2MintAllowanceSnapshot, uint256 l2MintAllowancePerBlock, uint256 lastL2MintAllowanceUpdateBlock ); - /// Emitted when tokens are minted due to an incoming transfer from L2 + + /** + * @notice Emitted when tokens are minted due to an incoming transfer from L2 + * @param amount Amount of tokens minted + */ event TokensMintedFromL2(uint256 amount); /** @@ -199,7 +251,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @dev Updates the L2 mint allowance per block + * @notice Updates the L2 mint allowance per block * It is meant to be called _after_ the issuancePerBlock is updated in L2. * The caller should provide the new issuance per block and the block at which it was updated, * the function will automatically compute the values so that the bridge's mint allowance @@ -221,7 +273,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @dev Manually sets the parameters used to compute the L2 mint allowance + * @notice Manually sets the parameters used to compute the L2 mint allowance * The use of this function is not recommended, use updateL2MintAllowance instead; * this one is only meant to be used as a backup recovery if a previous call to * updateL2MintAllowance was done with incorrect values. @@ -246,10 +298,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @notice Creates and sends a retryable ticket to transfer GRT to L2 using the Arbitrum Inbox. - * The tokens are escrowed by the gateway until they are withdrawn back to L1. - * The ticket must be redeemed on L2 to receive tokens at the specified address. - * Note that the caller must previously allow the gateway to spend the specified amount of GRT. + * @inheritdoc ITokenGateway * @dev maxGas and gasPriceBid must be set using Arbitrum's NodeInterface.estimateRetryableTicket method. * Also note that allowlisted senders (some protocol contracts) can include additional calldata * for a callhook to be executed on the L2 side when the tokens are received. In this case, the L2 transaction @@ -257,13 +306,6 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess * never succeeds. This requires extra care when adding contracts to the allowlist, but is necessary to ensure that * the tickets can be retried in the case of a temporary failure, and to ensure the atomicity of callhooks * with token transfers. - * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) - * @param _to Recipient address on L2 - * @param _amount Amount of tokens to transfer - * @param _maxGas Gas limit for L2 execution of the ticket - * @param _gasPriceBid Price per gas on L2 - * @param _data Encoded maxSubmissionCost and sender address along with additional calldata - * @return Sequence number of the retryable ticket created by Inbox */ function outboundTransfer( address _l1Token, @@ -304,15 +346,10 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @notice Receives withdrawn tokens from L2 - * The equivalent tokens are released from escrow and sent to the destination. + * @inheritdoc ITokenGateway * @dev can only accept transactions coming from the L2 GRT Gateway. * The last parameter is unused but kept for compatibility with Arbitrum gateways, * and the encoded exitNum is assumed to be 0. - * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) - * @param _from Address of the sender - * @param _to Recipient address on L1 - * @param _amount Amount of tokens transferred */ function finalizeInboundTransfer( address _l1Token, @@ -335,10 +372,8 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @notice Calculate the L2 address of a bridged token + * @inheritdoc ITokenGateway * @dev In our case, this would only work for GRT. - * @param _l1ERC20 address of L1 GRT contract - * @return L2 address of the bridged GRT token */ function calculateL2TokenAddress(address _l1ERC20) external view override returns (address) { IGraphToken token = graphToken(); @@ -387,10 +422,8 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess ); } - /** - * @dev Runs state validation before unpausing, reverts if - * something is not set properly - */ + /// @inheritdoc GraphTokenGateway + // solhint-disable-next-line use-natspec function _checksBeforeUnpause() internal view override { require(inbox != address(0), "INBOX_NOT_SET"); require(l1Router != address(0), "ROUTER_NOT_SET"); @@ -425,7 +458,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @dev Get the accumulated L2 mint allowance at a particular block number + * @notice Get the accumulated L2 mint allowance at a particular block number * @param _blockNum Block at which allowance will be computed * @return The accumulated GRT amount that can be minted from L2 at the specified block */ @@ -438,7 +471,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @dev Mint new L1 tokens coming from L2 + * @notice Mint new L1 tokens coming from L2 * This will check if the amount to mint is within the L2's mint allowance, and revert otherwise. * The tokens will be sent to the bridge escrow (from where they will then be sent to the destinatary * of the current inbound transfer). @@ -454,7 +487,7 @@ contract L1GraphTokenGateway is Initializable, GraphTokenGateway, L1ArbitrumMess } /** - * @dev Check if minting a certain amount of tokens from L2 is within allowance + * @notice Check if minting a certain amount of tokens from L2 is within allowance * @param _amount Number of tokens that would be minted * @return true if minting those tokens is allowed, or false if it would be over allowance */ diff --git a/packages/contracts/contracts/governance/Controller.sol b/packages/contracts/contracts/governance/Controller.sol index 707a27fff..c850542ab 100644 --- a/packages/contracts/contracts/governance/Controller.sol +++ b/packages/contracts/contracts/governance/Controller.sol @@ -2,21 +2,32 @@ pragma solidity ^0.7.6 || 0.8.27; -import { IController } from "./IController.sol"; -import { IManaged } from "./IManaged.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-small-strings +// solhint-disable named-parameters-mapping + +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + +import { IController } from "@graphprotocol/interfaces/contracts/contracts/governance/IController.sol"; +import { IManaged } from "@graphprotocol/interfaces/contracts/contracts/governance/IManaged.sol"; import { Governed } from "./Governed.sol"; import { Pausable } from "./Pausable.sol"; /** * @title Graph Controller contract - * @dev Controller is a registry of contracts for convenience. Inspired by Livepeer: + * @author Edge & Node + * @notice Controller is a registry of contracts for convenience. Inspired by Livepeer: * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol */ contract Controller is Governed, Pausable, IController { /// @dev Track contract ids to contract proxy address mapping(bytes32 => address) private _registry; - /// Emitted when the proxy address for a protocol contract has been set + /** + * @notice Emitted when the proxy address for a protocol contract has been set + * @param id Contract identifier + * @param contractAddress Address of the contract proxy + */ event SetContractProxy(bytes32 indexed id, address contractAddress); /** @@ -37,7 +48,7 @@ contract Controller is Governed, Pausable, IController { } /** - * @notice Getter to access governor + * @inheritdoc IController */ function getGovernor() external view override returns (address) { return governor; @@ -46,9 +57,7 @@ contract Controller is Governed, Pausable, IController { // -- Registry -- /** - * @notice Register contract id and mapped address - * @param _id Contract id (keccak256 hash of contract name) - * @param _contractAddress Contract address + * @inheritdoc IController */ function setContractProxy(bytes32 _id, address _contractAddress) external override onlyGovernor { require(_contractAddress != address(0), "Contract address must be set"); @@ -57,8 +66,7 @@ contract Controller is Governed, Pausable, IController { } /** - * @notice Unregister a contract address - * @param _id Contract id (keccak256 hash of contract name) + * @inheritdoc IController */ function unsetContractProxy(bytes32 _id) external override onlyGovernor { _registry[_id] = address(0); @@ -66,18 +74,14 @@ contract Controller is Governed, Pausable, IController { } /** - * @notice Get contract proxy address by its id - * @param _id Contract id - * @return Address of the proxy contract for the provided id + * @inheritdoc IController */ function getContractProxy(bytes32 _id) external view override returns (address) { return _registry[_id]; } /** - * @notice Update contract's controller - * @param _id Contract id (keccak256 hash of contract name) - * @param _controller Controller address + * @inheritdoc IController */ function updateController(bytes32 _id, address _controller) external override onlyGovernor { require(_controller != address(0), "Controller must be set"); @@ -96,17 +100,15 @@ contract Controller is Governed, Pausable, IController { } /** - * @notice Change the paused state of the contract - * Full pause most of protocol functions - * @param _toPause True if the contracts should be paused, false otherwise + * @inheritdoc IController + * @dev Full pause most of protocol functions */ function setPaused(bool _toPause) external override onlyGovernorOrGuardian { _setPaused(_toPause); } /** - * @notice Change the Pause Guardian - * @param _newPauseGuardian The address of the new Pause Guardian + * @inheritdoc IController */ function setPauseGuardian(address _newPauseGuardian) external override onlyGovernor { require(_newPauseGuardian != address(0), "PauseGuardian must be set"); @@ -114,16 +116,14 @@ contract Controller is Governed, Pausable, IController { } /** - * @notice Getter to access paused - * @return True if the contracts are paused, false otherwise + * @inheritdoc IController */ function paused() external view override returns (bool) { return _paused; } /** - * @notice Getter to access partial pause status - * @return True if the contracts are partially paused, false otherwise + * @inheritdoc IController */ function partialPaused() external view override returns (bool) { return _partialPaused; diff --git a/packages/contracts/contracts/governance/Governed.sol b/packages/contracts/contracts/governance/Governed.sol index 76a3247dd..8c3446b88 100644 --- a/packages/contracts/contracts/governance/Governed.sol +++ b/packages/contracts/contracts/governance/Governed.sol @@ -2,23 +2,39 @@ pragma solidity ^0.7.6 || 0.8.27; +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + /** * @title Graph Governance contract - * @dev All contracts that will be owned by a Governor entity should extend this contract. + * @author Edge & Node + * @notice All contracts that will be owned by a Governor entity should extend this contract. */ abstract contract Governed { // -- State -- - /// Address of the governor + /** + * @notice Address of the governor + */ address public governor; - /// Address of the new governor that is pending acceptance + /** + * @notice Address of the new governor that is pending acceptance + */ address public pendingGovernor; // -- Events -- - /// Emitted when a new owner/governor has been set, but is pending acceptance + /** + * @notice Emitted when a new owner/governor has been set, but is pending acceptance + * @param from Previous pending governor address + * @param to New pending governor address + */ event NewPendingOwnership(address indexed from, address indexed to); - /// Emitted when a new owner/governor has accepted their role + + /** + * @notice Emitted when a new owner/governor has accepted their role + * @param from Previous governor address + * @param to New governor address + */ event NewOwnership(address indexed from, address indexed to); /** @@ -30,7 +46,7 @@ abstract contract Governed { } /** - * @dev Initialize the governor for this contract + * @notice Initialize the governor for this contract * @param _initGovernor Address of the governor */ function _initialize(address _initGovernor) internal { diff --git a/packages/contracts/contracts/governance/IController.sol b/packages/contracts/contracts/governance/IController.sol deleted file mode 100644 index 6ab72010e..000000000 --- a/packages/contracts/contracts/governance/IController.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IController { - function getGovernor() external view returns (address); - - // -- Registry -- - - function setContractProxy(bytes32 _id, address _contractAddress) external; - - function unsetContractProxy(bytes32 _id) external; - - function updateController(bytes32 _id, address _controller) external; - - function getContractProxy(bytes32 _id) external view returns (address); - - // -- Pausing -- - - function setPartialPaused(bool _partialPaused) external; - - function setPaused(bool _paused) external; - - function setPauseGuardian(address _newPauseGuardian) external; - - function paused() external view returns (bool); - - function partialPaused() external view returns (bool); -} diff --git a/packages/contracts/contracts/governance/IManaged.sol b/packages/contracts/contracts/governance/IManaged.sol deleted file mode 100644 index ff6625d81..000000000 --- a/packages/contracts/contracts/governance/IManaged.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -import { IController } from "./IController.sol"; - -/** - * @title Managed Interface - * @dev Interface for contracts that can be managed by a controller. - */ -interface IManaged { - /** - * @notice Set the controller that manages this contract - * @dev Only the current controller can set a new controller - * @param _controller Address of the new controller - */ - function setController(address _controller) external; - - /** - * @notice Sync protocol contract addresses from the Controller registry - * @dev This function will cache all the contracts using the latest addresses. - * Anyone can call the function whenever a Proxy contract change in the - * controller to ensure the protocol is using the latest version. - */ - function syncAllContracts() external; - - /** - * @notice Get the Controller that manages this contract - * @return The Controller as an IController interface - */ - function controller() external view returns (IController); -} diff --git a/packages/contracts/contracts/governance/Managed.sol b/packages/contracts/contracts/governance/Managed.sol index 9b0ea29c8..c4718a1e6 100644 --- a/packages/contracts/contracts/governance/Managed.sol +++ b/packages/contracts/contracts/governance/Managed.sol @@ -2,21 +2,26 @@ pragma solidity ^0.7.6; -import { IController } from "./IController.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events +// solhint-disable named-parameters-mapping -import { ICuration } from "../curation/ICuration.sol"; -import { IEpochManager } from "../epochs/IEpochManager.sol"; -import { IRewardsManager } from "../rewards/IRewardsManager.sol"; -import { IStaking } from "../staking/IStaking.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; -import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; -import { IGNS } from "../discovery/IGNS.sol"; +import { IController } from "@graphprotocol/interfaces/contracts/contracts/governance/IController.sol"; -import { IManaged } from "./IManaged.sol"; +import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; +import { IEpochManager } from "@graphprotocol/interfaces/contracts/contracts/epochs/IEpochManager.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; +import { IStaking } from "@graphprotocol/interfaces/contracts/contracts/staking/IStaking.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; +import { IGNS } from "@graphprotocol/interfaces/contracts/contracts/discovery/IGNS.sol"; + +import { IManaged } from "@graphprotocol/interfaces/contracts/contracts/governance/IManaged.sol"; /** * @title Graph Managed contract - * @dev The Managed contract provides an interface to interact with the Controller. + * @author Edge & Node + * @notice The Managed contract provides an interface to interact with the Controller. * It also provides local caching for contract addresses. This mechanism relies on calling the * public `syncAllContracts()` function whenever a contract changes in the controller. * @@ -26,7 +31,9 @@ import { IManaged } from "./IManaged.sol"; abstract contract Managed is IManaged { // -- State -- - /// Controller that manages this contract + /** + * @inheritdoc IManaged + */ IController public override controller; /// @dev Cache for the addresses of the contracts retrieved from the controller mapping(bytes32 => address) private _addressCache; @@ -34,28 +41,46 @@ abstract contract Managed is IManaged { uint256[10] private __gap; // Immutables + /// @dev Contract name hash for Curation contract bytes32 private immutable CURATION = keccak256("Curation"); + /// @dev Contract name hash for EpochManager contract bytes32 private immutable EPOCH_MANAGER = keccak256("EpochManager"); + /// @dev Contract name hash for RewardsManager contract bytes32 private immutable REWARDS_MANAGER = keccak256("RewardsManager"); + /// @dev Contract name hash for Staking contract bytes32 private immutable STAKING = keccak256("Staking"); + /// @dev Contract name hash for GraphToken contract bytes32 private immutable GRAPH_TOKEN = keccak256("GraphToken"); + /// @dev Contract name hash for GraphTokenGateway contract bytes32 private immutable GRAPH_TOKEN_GATEWAY = keccak256("GraphTokenGateway"); + /// @dev Contract name hash for GNS contract bytes32 private immutable GNS = keccak256("GNS"); // -- Events -- - /// Emitted when a contract parameter has been updated + /** + * @notice Emitted when a contract parameter has been updated + * @param param Name of the parameter that was updated + */ event ParameterUpdated(string param); - /// Emitted when the controller address has been set + + /** + * @notice Emitted when the controller address has been set + * @param controller Address of the new controller + */ event SetController(address controller); - /// Emitted when contract with `nameHash` is synced to `contractAddress`. + /** + * @notice Emitted when contract with `nameHash` is synced to `contractAddress`. + * @param nameHash Hash of the contract name + * @param contractAddress Address of the synced contract + */ event ContractSynced(bytes32 indexed nameHash, address contractAddress); // -- Modifiers -- /** - * @dev Revert if the controller is paused or partially paused + * @notice Revert if the controller is paused or partially paused */ function _notPartialPaused() internal view { require(!controller.paused(), "Paused"); @@ -63,21 +88,21 @@ abstract contract Managed is IManaged { } /** - * @dev Revert if the controller is paused + * @notice Revert if the controller is paused */ function _notPaused() internal view virtual { require(!controller.paused(), "Paused"); } /** - * @dev Revert if the caller is not the governor + * @notice Revert if the caller is not the governor */ function _onlyGovernor() internal view { require(msg.sender == controller.getGovernor(), "Only Controller governor"); } /** - * @dev Revert if the caller is not the Controller + * @notice Revert if the caller is not the Controller */ function _onlyController() internal view { require(msg.sender == address(controller), "Caller must be Controller"); @@ -118,7 +143,7 @@ abstract contract Managed is IManaged { // -- Functions -- /** - * @dev Initialize a Managed contract + * @notice Initialize a Managed contract * @param _controller Address for the Controller that manages this contract */ function _initialize(address _controller) internal { @@ -126,15 +151,14 @@ abstract contract Managed is IManaged { } /** - * @notice Set Controller. Only callable by current controller. - * @param _controller Controller contract address + * @inheritdoc IManaged */ function setController(address _controller) external override onlyController { _setController(_controller); } /** - * @dev Set controller. + * @notice Set controller. * @param _controller Controller contract address */ function _setController(address _controller) internal { @@ -144,7 +168,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return Curation interface + * @notice Return Curation interface * @return Curation contract registered with Controller */ function curation() internal view returns (ICuration) { @@ -152,7 +176,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return EpochManager interface + * @notice Return EpochManager interface * @return Epoch manager contract registered with Controller */ function epochManager() internal view returns (IEpochManager) { @@ -160,7 +184,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return RewardsManager interface + * @notice Return RewardsManager interface * @return Rewards manager contract registered with Controller */ function rewardsManager() internal view returns (IRewardsManager) { @@ -168,7 +192,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return Staking interface + * @notice Return Staking interface * @return Staking contract registered with Controller */ function staking() internal view returns (IStaking) { @@ -176,7 +200,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return GraphToken interface + * @notice Return GraphToken interface * @return Graph token contract registered with Controller */ function graphToken() internal view returns (IGraphToken) { @@ -184,7 +208,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return GraphTokenGateway (L1 or L2) interface + * @notice Return GraphTokenGateway (L1 or L2) interface * @return Graph token gateway contract registered with Controller */ function graphTokenGateway() internal view returns (ITokenGateway) { @@ -192,7 +216,7 @@ abstract contract Managed is IManaged { } /** - * @dev Return GNS (L1 or L2) interface. + * @notice Return GNS (L1 or L2) interface. * @return Address of the GNS contract registered with Controller, as an IGNS interface. */ function gns() internal view returns (IGNS) { @@ -200,7 +224,7 @@ abstract contract Managed is IManaged { } /** - * @dev Resolve a contract address from the cache or the Controller if not found. + * @notice Resolve a contract address from the cache or the Controller if not found. * @param _nameHash keccak256 hash of the contract name * @return Address of the contract */ @@ -213,7 +237,7 @@ abstract contract Managed is IManaged { } /** - * @dev Cache a contract address from the Controller registry. + * @notice Cache a contract address from the Controller registry. * @param _nameHash keccak256 hash of the name of the contract to sync into the cache */ function _syncContract(bytes32 _nameHash) internal { @@ -225,10 +249,7 @@ abstract contract Managed is IManaged { } /** - * @notice Sync protocol contract addresses from the Controller registry - * @dev This function will cache all the contracts using the latest addresses - * Anyone can call the function whenever a Proxy contract change in the - * controller to ensure the protocol is using the latest version + * @inheritdoc IManaged */ function syncAllContracts() external override { _syncContract(CURATION); diff --git a/packages/contracts/contracts/governance/Pausable.sol b/packages/contracts/contracts/governance/Pausable.sol index 2bc1795cd..bf260cb72 100644 --- a/packages/contracts/contracts/governance/Pausable.sol +++ b/packages/contracts/contracts/governance/Pausable.sol @@ -2,6 +2,14 @@ pragma solidity ^0.7.6 || 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + +/** + * @title Pausable Contract + * @author Edge & Node + * @notice Abstract contract that provides pause functionality for protocol operations + */ abstract contract Pausable { /** * @dev "Partial paused" pauses exit and enter functions for GRT, but not internal @@ -13,24 +21,36 @@ abstract contract Pausable { */ bool internal _paused; - /// Timestamp for the last time the partial pause was set + /// @notice Timestamp for the last time the partial pause was set uint256 public lastPartialPauseTime; - /// Timestamp for the last time the full pause was set + /// @notice Timestamp for the last time the full pause was set uint256 public lastPauseTime; - /// Pause guardian is a separate entity from the governor that can + /// @notice Pause guardian is a separate entity from the governor that can /// pause and unpause the protocol, fully or partially address public pauseGuardian; - /// Emitted when the partial pause state changed + /** + * @notice Emitted when the partial pause state changed + * @param isPaused Whether the contract is partially paused + */ event PartialPauseChanged(bool isPaused); - /// Emitted when the full pause state changed + + /** + * @notice Emitted when the full pause state changed + * @param isPaused Whether the contract is fully paused + */ event PauseChanged(bool isPaused); - /// Emitted when the pause guardian is changed + + /** + * @notice Emitted when the pause guardian is changed + * @param oldPauseGuardian Address of the previous pause guardian + * @param pauseGuardian Address of the new pause guardian + */ event NewPauseGuardian(address indexed oldPauseGuardian, address indexed pauseGuardian); /** - * @dev Change the partial paused state of the contract + * @notice Change the partial paused state of the contract * @param _toPartialPause New value for the partial pause state (true means the contracts will be partially paused) */ function _setPartialPaused(bool _toPartialPause) internal { @@ -45,7 +65,7 @@ abstract contract Pausable { } /** - * @dev Change the paused state of the contract + * @notice Change the paused state of the contract * @param _toPause New value for the pause state (true means the contracts will be paused) */ function _setPaused(bool _toPause) internal { @@ -60,7 +80,7 @@ abstract contract Pausable { } /** - * @dev Change the Pause Guardian + * @notice Change the Pause Guardian * @param newPauseGuardian The address of the new Pause Guardian */ function _setPauseGuardian(address newPauseGuardian) internal { diff --git a/packages/contracts/contracts/l2/curation/IL2Curation.sol b/packages/contracts/contracts/l2/curation/IL2Curation.sol deleted file mode 100644 index 7f93f9603..000000000 --- a/packages/contracts/contracts/l2/curation/IL2Curation.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -/** - * @title Interface of the L2 Curation contract. - */ -interface IL2Curation { - /** - * @notice Set the subgraph service address. - * @param _subgraphService Address of the SubgraphService contract - */ - function setSubgraphService(address _subgraphService) external; - - /** - * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. - * @dev This function charges no tax and can only be called by GNS in specific scenarios (for now - * only during an L1-L2 transfer). - * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal - * @param _tokensIn Amount of Graph Tokens to deposit - * @return Signal minted - */ - function mintTaxFree(bytes32 _subgraphDeploymentID, uint256 _tokensIn) external returns (uint256); - - /** - * @notice Calculate amount of signal that can be bought with tokens in a curation pool, - * without accounting for curation tax. - * @param _subgraphDeploymentID Subgraph deployment for which to mint signal - * @param _tokensIn Amount of tokens used to mint signal - * @return Amount of signal that can be bought - */ - function tokensToSignalNoTax(bytes32 _subgraphDeploymentID, uint256 _tokensIn) external view returns (uint256); - - /** - * @notice Calculate the amount of tokens that would be recovered if minting signal with - * the input tokens and then burning it. This can be used to compute rounding error. - * This function does not account for curation tax. - * @param _subgraphDeploymentID Subgraph deployment for which to mint signal - * @param _tokensIn Amount of tokens used to mint signal - * @return Amount of tokens that would be recovered after minting and burning signal - */ - function tokensToSignalToTokensNoTax( - bytes32 _subgraphDeploymentID, - uint256 _tokensIn - ) external view returns (uint256); -} diff --git a/packages/contracts/contracts/l2/curation/L2Curation.sol b/packages/contracts/contracts/l2/curation/L2Curation.sol index 271545ea7..56e83c13a 100644 --- a/packages/contracts/contracts/l2/curation/L2Curation.sol +++ b/packages/contracts/contracts/l2/curation/L2Curation.sol @@ -3,22 +3,26 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-small-strings, gas-strict-inequalities + import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { ClonesUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol"; import { GraphUpgradeable } from "../../upgrades/GraphUpgradeable.sol"; import { TokenUtils } from "../../utils/TokenUtils.sol"; -import { IRewardsManager } from "../../rewards/IRewardsManager.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; import { Managed } from "../../governance/Managed.sol"; -import { IGraphToken } from "../../token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { CurationV3Storage } from "../../curation/CurationStorage.sol"; -import { IGraphCurationToken } from "../../curation/IGraphCurationToken.sol"; -import { IL2Curation } from "./IL2Curation.sol"; +import { IGraphCurationToken } from "@graphprotocol/interfaces/contracts/contracts/curation/IGraphCurationToken.sol"; +import { IL2Curation } from "@graphprotocol/interfaces/contracts/contracts/l2/curation/IL2Curation.sol"; /** * @title L2Curation contract - * @dev Allows curators to signal on subgraph deployments that might be relevant to indexers by + * @author Edge & Node + * @notice Allows curators to signal on subgraph deployments that might be relevant to indexers by * staking Graph Tokens (GRT). Additionally, curators earn fees from the Query Market related to the * subgraph deployment they curate. * A curators deposit goes to a curation pool along with the deposits of other curators, @@ -38,14 +42,20 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { uint256 private constant SIGNAL_PER_MINIMUM_DEPOSIT = 1; // 1e-18 signal as 18 decimal number /// @dev Reserve ratio for all subgraphs set to 100% for a flat bonding curve + // solhint-disable-next-line immutable-vars-naming uint32 private immutable fixedReserveRatio = MAX_PPM; // -- Events -- /** - * @dev Emitted when `curator` deposited `tokens` on `subgraphDeploymentID` as curation signal. + * @notice Emitted when `curator` deposited `tokens` on `subgraphDeploymentID` as curation signal. * The `curator` receives `signal` amount according to the curation pool bonding curve. * An amount of `curationTax` will be collected and burned. + * @param curator Address of the curator + * @param subgraphDeploymentID Subgraph deployment being signaled on + * @param tokens Amount of tokens deposited + * @param signal Amount of signal minted + * @param curationTax Amount of tokens burned as curation tax */ event Signalled( address indexed curator, @@ -56,19 +66,26 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { ); /** - * @dev Emitted when `curator` burned `signal` for a `subgraphDeploymentID`. + * @notice Emitted when `curator` burned `signal` for a `subgraphDeploymentID`. * The curator will receive `tokens` according to the value of the bonding curve. + * @param curator Address of the curator + * @param subgraphDeploymentID Subgraph deployment being signaled on + * @param tokens Amount of tokens received + * @param signal Amount of signal burned */ event Burned(address indexed curator, bytes32 indexed subgraphDeploymentID, uint256 tokens, uint256 signal); /** - * @dev Emitted when `tokens` amount were collected for `subgraphDeploymentID` as part of fees + * @notice Emitted when `tokens` amount were collected for `subgraphDeploymentID` as part of fees * distributed by an indexer from query fees received from state channels. + * @param subgraphDeploymentID Subgraph deployment that collected fees + * @param tokens Amount of tokens collected as fees */ event Collected(bytes32 indexed subgraphDeploymentID, uint256 tokens); /** - * @dev Emitted when the subgraph service is set. + * @notice Emitted when the subgraph service is set + * @param newSubgraphService Address of the new subgraph service */ event SubgraphServiceSet(address indexed newSubgraphService); @@ -107,7 +124,8 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { * @notice Set the default reserve ratio - not implemented in L2 * @dev We only keep this for compatibility with ICuration */ - function setDefaultReserveRatio(uint32) external view override onlyGovernor { + // solhint-disable-next-line use-natspec + function setDefaultReserveRatio(uint32 /* _defaultReserveRatio */) external view override onlyGovernor { revert("Not implemented in L2"); } @@ -153,7 +171,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { * @param _tokens Amount of Graph Tokens to add to reserves */ function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external override { - // Only SubgraphService or Staking contract are authorized as caller + // Only SubgraphService and Staking contract are authorized as callers require( msg.sender == subgraphService || msg.sender == address(staking()), "Caller must be the subgraph service or staking contract" @@ -174,7 +192,8 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal * @param _tokensIn Amount of Graph Tokens to deposit * @param _signalOutMin Expected minimum amount of signal to receive - * @return Signal minted and deposit tax + * @return Signal minted + * @return Curation tax paid */ function mint( bytes32 _subgraphDeploymentID, @@ -228,12 +247,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @notice Deposit Graph Tokens in exchange for signal of a SubgraphDeployment curation pool. - * @dev This function charges no tax and can only be called by GNS in specific scenarios (for now - * only during an L1-L2 transfer). - * @param _subgraphDeploymentID Subgraph deployment pool from where to mint signal - * @param _tokensIn Amount of Graph Tokens to deposit - * @return Signal minted + * @inheritdoc IL2Curation */ function mintTaxFree( bytes32 _subgraphDeploymentID, @@ -387,11 +401,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @notice Calculate amount of signal that can be bought with tokens in a curation pool, - * without accounting for curation tax. - * @param _subgraphDeploymentID Subgraph deployment to mint signal - * @param _tokensIn Amount of tokens used to mint signal - * @return Amount of signal that can be bought + * @inheritdoc IL2Curation */ function tokensToSignalNoTax( bytes32 _subgraphDeploymentID, @@ -401,12 +411,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @notice Calculate the amount of tokens that would be recovered if minting signal with - * the input tokens and then burning it. This can be used to compute rounding error. - * This function does not account for curation tax. - * @param _subgraphDeploymentID Subgraph deployment for which to mint signal - * @param _tokensIn Amount of tokens used to mint signal - * @return Amount of tokens that would be recovered after minting and burning signal + * @inheritdoc IL2Curation */ function tokensToSignalToTokensNoTax( bytes32 _subgraphDeploymentID, @@ -436,7 +441,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @dev Internal: Set the minimum deposit amount for curators. + * @notice Internal: Set the minimum deposit amount for curators. * Update the minimum deposit amount to `_minimumCurationDeposit` * @param _minimumCurationDeposit Minimum amount of tokens required deposit */ @@ -448,7 +453,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @dev Internal: Set the curation tax percentage to charge when a curator deposits GRT tokens. + * @notice Internal: Set the curation tax percentage to charge when a curator deposits GRT tokens. * @param _percentage Curation tax percentage charged when depositing GRT tokens */ function _setCurationTaxPercentage(uint32 _percentage) private { @@ -459,7 +464,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @dev Internal: Set the master copy to use as clones for the curation token. + * @notice Internal: Set the master copy to use as clones for the curation token. * @param _curationTokenMaster Address of implementation contract to use for curation tokens */ function _setCurationTokenMaster(address _curationTokenMaster) private { @@ -471,7 +476,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @dev Triggers an update of rewards due to a change in signal. + * @notice Triggers an update of rewards due to a change in signal. * @param _subgraphDeploymentID Subgraph deployment updated */ function _updateRewards(bytes32 _subgraphDeploymentID) private { @@ -482,7 +487,7 @@ contract L2Curation is CurationV3Storage, GraphUpgradeable, IL2Curation { } /** - * @dev Calculate amount of signal that can be bought with tokens in a curation pool. + * @notice Calculate amount of signal that can be bought with tokens in a curation pool. * @param _subgraphDeploymentID Subgraph deployment to mint signal * @param _tokensIn Amount of tokens used to mint signal * @return Amount of signal that can be bought with tokens diff --git a/packages/contracts/contracts/l2/discovery/IL2GNS.sol b/packages/contracts/contracts/l2/discovery/IL2GNS.sol deleted file mode 100644 index a24216fbb..000000000 --- a/packages/contracts/contracts/l2/discovery/IL2GNS.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; - -/** - * @title Interface for the L2GNS contract. - */ -interface IL2GNS is ICallhookReceiver { - enum L1MessageCodes { - RECEIVE_SUBGRAPH_CODE, - RECEIVE_CURATOR_BALANCE_CODE - } - - /** - * @dev The SubgraphL2TransferData struct holds information - * about a subgraph related to its transfer from L1 to L2. - */ - struct SubgraphL2TransferData { - uint256 tokens; // GRT that will be sent to L2 to mint signal - mapping(address => bool) curatorBalanceClaimed; // True for curators whose balance has been claimed in L2 - bool l2Done; // Transfer finished on L2 side - uint256 subgraphReceivedOnL2BlockNumber; // Block number when the subgraph was received on L2 - } - - /** - * @notice Finish a subgraph transfer from L1. - * The subgraph must have been previously sent through the bridge - * using the sendSubgraphToL2 function on L1GNS. - * @param _l2SubgraphID Subgraph ID in L2 (aliased from the L1 subgraph ID) - * @param _subgraphDeploymentID Latest subgraph deployment to assign to the subgraph - * @param _subgraphMetadata IPFS hash of the subgraph metadata - * @param _versionMetadata IPFS hash of the version metadata - */ - function finishSubgraphTransferFromL1( - uint256 _l2SubgraphID, - bytes32 _subgraphDeploymentID, - bytes32 _subgraphMetadata, - bytes32 _versionMetadata - ) external; - - /** - * @notice Return the aliased L2 subgraph ID from a transferred L1 subgraph ID - * @param _l1SubgraphID L1 subgraph ID - * @return L2 subgraph ID - */ - function getAliasedL2SubgraphID(uint256 _l1SubgraphID) external pure returns (uint256); - - /** - * @notice Return the unaliased L1 subgraph ID from a transferred L2 subgraph ID - * @param _l2SubgraphID L2 subgraph ID - * @return L1subgraph ID - */ - function getUnaliasedL1SubgraphID(uint256 _l2SubgraphID) external pure returns (uint256); -} diff --git a/packages/contracts/contracts/l2/discovery/L2GNS.sol b/packages/contracts/contracts/l2/discovery/L2GNS.sol index 34d47d400..bd176dbcf 100644 --- a/packages/contracts/contracts/l2/discovery/L2GNS.sol +++ b/packages/contracts/contracts/l2/discovery/L2GNS.sol @@ -3,18 +3,22 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, gas-small-strings, gas-strict-inequalities + import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { GNS } from "../../discovery/GNS.sol"; -import { ICuration } from "../../curation/ICuration.sol"; -import { IL2GNS } from "./IL2GNS.sol"; +import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; +import { IL2GNS } from "@graphprotocol/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol"; import { L2GNSV1Storage } from "./L2GNSStorage.sol"; -import { IL2Curation } from "../curation/IL2Curation.sol"; +import { IL2Curation } from "@graphprotocol/interfaces/contracts/contracts/l2/curation/IL2Curation.sol"; /** * @title L2GNS - * @dev The Graph Name System contract provides a decentralized naming system for subgraphs + * @author Edge & Node + * @notice The Graph Name System contract provides a decentralized naming system for subgraphs * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. * Each version is associated with a Subgraph Deployment. The contract has no knowledge of * human-readable names. All human readable names emitted in events. @@ -26,35 +30,47 @@ import { IL2Curation } from "../curation/IL2Curation.sol"; contract L2GNS is GNS, L2GNSV1Storage, IL2GNS { using SafeMathUpgradeable for uint256; - /// Offset added to an L1 subgraph ID to compute the L2 subgraph ID alias + /// @notice Offset added to an L1 subgraph ID to compute the L2 subgraph ID alias uint256 public constant SUBGRAPH_ID_ALIAS_OFFSET = uint256(0x1111000000000000000000000000000000000000000000000000000000001111); - /// Maximum rounding error when receiving signal tokens from L1, in parts-per-million. - /// If the error from minting signal is above this, tokens will be sent back to the curator. + /// @notice Maximum rounding error when receiving signal tokens from L1, in parts-per-million + /// @dev If the error from minting signal is above this, tokens will be sent back to the curator uint256 public constant MAX_ROUNDING_ERROR = 1000; /// @dev 100% expressed in parts-per-million uint256 private constant MAX_PPM = 1000000; - /// @dev Emitted when a subgraph is received from L1 through the bridge + /// @notice Emitted when a subgraph is received from L1 through the bridge + /// @param _l1SubgraphID Subgraph ID on L1 + /// @param _l2SubgraphID Subgraph ID on L2 (aliased) + /// @param _owner Address of the subgraph owner + /// @param _tokens Amount of tokens transferred with the subgraph event SubgraphReceivedFromL1( uint256 indexed _l1SubgraphID, uint256 indexed _l2SubgraphID, address indexed _owner, uint256 _tokens ); - /// @dev Emitted when a subgraph transfer from L1 is finalized, so the subgraph is published on L2 + /// @notice Emitted when a subgraph transfer from L1 is finalized, so the subgraph is published on L2 + /// @param _l2SubgraphID Subgraph ID on L2 event SubgraphL2TransferFinalized(uint256 indexed _l2SubgraphID); - /// @dev Emitted when the L1 balance for a curator has been claimed + /// @notice Emitted when the L1 balance for a curator has been claimed + /// @param _l1SubgraphId Subgraph ID on L1 + /// @param _l2SubgraphID Subgraph ID on L2 (aliased) + /// @param _l2Curator Address of the curator on L2 + /// @param _tokens Amount of tokens received event CuratorBalanceReceived( uint256 indexed _l1SubgraphId, uint256 indexed _l2SubgraphID, address indexed _l2Curator, uint256 _tokens ); - /// @dev Emitted when the L1 balance for a curator has been returned to the beneficiary. + /// @notice Emitted when the L1 balance for a curator has been returned to the beneficiary. /// This can happen if the subgraph transfer was not finished when the curator's tokens arrived. + /// @param _l1SubgraphID Subgraph ID on L1 + /// @param _l2Curator Address of the curator on L2 + /// @param _tokens Amount of tokens returned event CuratorBalanceReturnedToBeneficiary( uint256 indexed _l1SubgraphID, address indexed _l2Curator, @@ -103,13 +119,7 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS { } /** - * @notice Finish a subgraph transfer from L1. - * The subgraph must have been previously sent through the bridge - * using the sendSubgraphToL2 function on L1GNS. - * @param _l2SubgraphID Subgraph ID (aliased from the L1 subgraph ID) - * @param _subgraphDeploymentID Latest subgraph deployment to assign to the subgraph - * @param _subgraphMetadata IPFS hash of the subgraph metadata - * @param _versionMetadata IPFS hash of the version metadata + * @inheritdoc IL2GNS */ function finishSubgraphTransferFromL1( uint256 _l2SubgraphID, @@ -220,25 +230,21 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS { } /** - * @notice Return the aliased L2 subgraph ID from a transferred L1 subgraph ID - * @param _l1SubgraphID L1 subgraph ID - * @return L2 subgraph ID + * @inheritdoc IL2GNS */ function getAliasedL2SubgraphID(uint256 _l1SubgraphID) public pure override returns (uint256) { return _l1SubgraphID + SUBGRAPH_ID_ALIAS_OFFSET; } /** - * @notice Return the unaliased L1 subgraph ID from a transferred L2 subgraph ID - * @param _l2SubgraphID L2 subgraph ID - * @return L1subgraph ID + * @inheritdoc IL2GNS */ function getUnaliasedL1SubgraphID(uint256 _l2SubgraphID) public pure override returns (uint256) { return _l2SubgraphID - SUBGRAPH_ID_ALIAS_OFFSET; } /** - * @dev Receive a subgraph from L1. + * @notice Receive a subgraph from L1. * This function will initialize a subgraph received through the bridge, * and store the transfer data so that it's finalized later using finishSubgraphTransferFromL1. * @param _l1SubgraphID Subgraph ID in L1 (will be aliased) @@ -308,9 +314,9 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS { } /** - * @dev Get subgraph data. - * Since there are no legacy subgraphs in L2, we override the base - * GNS method to save us the step of checking for legacy subgraphs. + * @notice Get subgraph data + * @dev Since there are no legacy subgraphs in L2, we override the base + * GNS method to save us the step of checking for legacy subgraphs * @param _subgraphID Subgraph ID * @return Subgraph Data */ diff --git a/packages/contracts/contracts/l2/discovery/L2GNSStorage.sol b/packages/contracts/contracts/l2/discovery/L2GNSStorage.sol index f658c49d9..d464ea891 100644 --- a/packages/contracts/contracts/l2/discovery/L2GNSStorage.sol +++ b/packages/contracts/contracts/l2/discovery/L2GNSStorage.sol @@ -1,17 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6; pragma abicoder v2; -import { IL2GNS } from "./IL2GNS.sol"; +import { IL2GNS } from "@graphprotocol/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol"; /** * @title L2GNSV1Storage + * @author Edge & Node * @notice This contract holds all the L2-specific storage variables for the L2GNS contract, version 1 - * @dev */ abstract contract L2GNSV1Storage { - /// Data for subgraph transfer from L1 to L2 + /// @notice Data for subgraph transfer from L1 to L2 mapping(uint256 => IL2GNS.SubgraphL2TransferData) public subgraphL2TransferData; /// @dev Storage gap to keep storage slots fixed in future versions uint256[50] private __gap; diff --git a/packages/contracts/contracts/l2/gateway/L2GraphTokenGateway.sol b/packages/contracts/contracts/l2/gateway/L2GraphTokenGateway.sol index be8f212b8..aa8868c49 100644 --- a/packages/contracts/contracts/l2/gateway/L2GraphTokenGateway.sol +++ b/packages/contracts/contracts/l2/gateway/L2GraphTokenGateway.sol @@ -3,20 +3,24 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import { L2ArbitrumMessenger } from "../../arbitrum/L2ArbitrumMessenger.sol"; import { AddressAliasHelper } from "../../arbitrum/AddressAliasHelper.sol"; -import { ITokenGateway } from "../../arbitrum/ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { Managed } from "../../governance/Managed.sol"; import { GraphTokenGateway } from "../../gateway/GraphTokenGateway.sol"; -import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; +import { ICallhookReceiver } from "@graphprotocol/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol"; import { L2GraphToken } from "../token/L2GraphToken.sol"; /** * @title L2 Graph Token Gateway Contract - * @dev Provides the L2 side of the Ethereum-Arbitrum GRT bridge. Receives GRT from the L1 chain + * @author Edge & Node + * @notice Provides the L2 side of the Ethereum-Arbitrum GRT bridge. Receives GRT from the L1 chain * and mints them on the L2 side. Sends GRT back to L1 by burning them on the L2 side. * Based on Offchain Labs' reference implementation and Livepeer's arbitrum-lpt-bridge * (See: https://github.com/OffchainLabs/arbitrum/tree/master/packages/arb-bridge-peripherals/contracts/tokenbridge @@ -25,23 +29,42 @@ import { L2GraphToken } from "../token/L2GraphToken.sol"; contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, ReentrancyGuardUpgradeable { using SafeMathUpgradeable for uint256; - /// Address of the Graph Token contract on L1 + /// @notice Address of the Graph Token contract on L1 address public l1GRT; - /// Address of the L1GraphTokenGateway that is the counterpart of this gateway on L1 + /// @notice Address of the L1GraphTokenGateway that is the counterpart of this gateway on L1 address public l1Counterpart; - /// Address of the Arbitrum Gateway Router on L2 + /// @notice Address of the Arbitrum Gateway Router on L2 address public l2Router; /// @dev Calldata included in an outbound transfer, stored as a structure for convenience and stack depth + /** + * @dev Struct for outbound transfer calldata + * @param from Address sending the tokens + * @param extraData Additional data for the transfer + */ struct OutboundCalldata { address from; bytes extraData; } - /// Emitted when an incoming transfer is finalized, i.e. tokens were deposited from L1 to L2 + /** + * @notice Emitted when an incoming transfer is finalized, i.e. tokens were deposited from L1 to L2 + * @param l1Token Address of the L1 token + * @param from Address sending the tokens on L1 + * @param to Address receiving the tokens on L2 + * @param amount Amount of tokens transferred + */ event DepositFinalized(address indexed l1Token, address indexed from, address indexed to, uint256 amount); - /// Emitted when an outbound transfer is initiated, i.e. tokens are being withdrawn from L2 back to L1 + /** + * @notice Emitted when an outbound transfer is initiated, i.e. tokens are being withdrawn from L2 back to L1 + * @param l1Token Address of the L1 token + * @param from Address sending the tokens on L2 + * @param to Address receiving the tokens on L1 + * @param l2ToL1Id ID of the L2 to L1 message + * @param exitNum Exit number (always 0 for this contract) + * @param amount Amount of tokens transferred + */ event WithdrawalInitiated( address l1Token, address indexed from, @@ -51,11 +74,22 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran uint256 amount ); - /// Emitted when the Arbitrum Gateway Router address on L2 has been updated + /** + * @notice Emitted when the Arbitrum Gateway Router address on L2 has been updated + * @param l2Router Address of the L2 Gateway Router + */ event L2RouterSet(address l2Router); - /// Emitted when the L1 Graph Token address has been updated + + /** + * @notice Emitted when the L1 Graph Token address has been updated + * @param l1GRT Address of the L1 GRT contract + */ event L1TokenAddressSet(address l1GRT); - /// Emitted when the address of the counterpart gateway on L1 has been updated + + /** + * @notice Emitted when the address of the counterpart gateway on L1 has been updated + * @param l1Counterpart Address of the L1 counterpart gateway + */ event L1CounterpartAddressSet(address l1Counterpart); /** @@ -135,7 +169,7 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran } /** - * @notice Receives token amount from L1 and mints the equivalent tokens to the receiving address + * @inheritdoc ITokenGateway * @dev Only accepts transactions from the L1 GRT Gateway. * The function is payable for ITokenGateway compatibility, but msg.value must be zero. * Note that allowlisted senders (some protocol contracts) can include additional calldata @@ -144,11 +178,6 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran * never succeeds. This requires extra care when adding contracts to the allowlist, but is necessary to ensure that * the tickets can be retried in the case of a temporary failure, and to ensure the atomicity of callhooks * with token transfers. - * @param _l1Token L1 Address of GRT - * @param _from Address of the sender on L1 - * @param _to Recipient address on L2 - * @param _amount Amount of tokens transferred - * @param _data Extra callhook data, only used when the sender is allowlisted */ function finalizeInboundTransfer( address _l1Token, @@ -170,18 +199,14 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran } /** - * @notice Burns L2 tokens and initiates a transfer to L1. + * @inheritdoc ITokenGateway + * @dev Burns L2 tokens and initiates a transfer to L1. * The tokens will be available on L1 only after the wait period (7 days) is over, * and will require an Outbox.executeTransaction to finalize. * Note that the caller must previously allow the gateway to spend the specified amount of GRT. - * @dev no additional callhook data is allowed. The two unused params are needed + * No additional callhook data is allowed. The two unused params are needed * for compatibility with Arbitrum's gateway router. * The function is payable for ITokenGateway compatibility, but msg.value must be zero. - * @param _l1Token L1 Address of GRT (needed for compatibility with Arbitrum Gateway Router) - * @param _to Recipient address on L1 - * @param _amount Amount of tokens to burn - * @param _data Contains sender and additional data (always empty) to send to L1 - * @return ID of the withdraw transaction */ function outboundTransfer( address _l1Token, @@ -218,10 +243,8 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran } /** - * @notice Calculate the L2 address of a bridged token + * @inheritdoc ITokenGateway * @dev In our case, this would only work for GRT. - * @param l1ERC20 address of L1 GRT contract - * @return L2 address of the bridged GRT token */ function calculateL2TokenAddress(address l1ERC20) public view override returns (address) { if (l1ERC20 != l1GRT) { @@ -259,10 +282,8 @@ contract L2GraphTokenGateway is GraphTokenGateway, L2ArbitrumMessenger, Reentran ); } - /** - * @dev Runs state validation before unpausing, reverts if - * something is not set properly - */ + /// @inheritdoc GraphTokenGateway + // solhint-disable-next-line use-natspec function _checksBeforeUnpause() internal view override { require(l2Router != address(0), "L2_ROUTER_NOT_SET"); require(l1Counterpart != address(0), "L1_COUNTERPART_NOT_SET"); diff --git a/packages/contracts/contracts/l2/staking/IL2Staking.sol b/packages/contracts/contracts/l2/staking/IL2Staking.sol deleted file mode 100644 index 4b7748e31..000000000 --- a/packages/contracts/contracts/l2/staking/IL2Staking.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0; -pragma abicoder v2; - -import { IStaking } from "../../staking/IStaking.sol"; -import { IL2StakingBase } from "./IL2StakingBase.sol"; -import { IL2StakingTypes } from "./IL2StakingTypes.sol"; - -/** - * @title Interface for the L2 Staking contract - * @notice This is the interface that should be used when interacting with the L2 Staking contract. - * It extends the IStaking interface with the functions that are specific to L2, adding the callhook receiver - * to receive transferred stake and delegation from L1. - * @dev Note that L2Staking doesn't actually inherit this interface. This is because of - * the custom setup of the Staking contract where part of the functionality is implemented - * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. - */ -interface IL2Staking is IStaking, IL2StakingBase, IL2StakingTypes {} diff --git a/packages/contracts/contracts/l2/staking/IL2StakingBase.sol b/packages/contracts/contracts/l2/staking/IL2StakingBase.sol deleted file mode 100644 index f5c33c2d0..000000000 --- a/packages/contracts/contracts/l2/staking/IL2StakingBase.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; - -/** - * @title Base interface for the L2Staking contract. - * @notice This interface is used to define the callhook receiver interface that is implemented by L2Staking. - * @dev Note it includes only the L2-specific functionality, not the full IStaking interface. - */ -interface IL2StakingBase is ICallhookReceiver { - event TransferredDelegationReturnedToDelegator(address indexed indexer, address indexed delegator, uint256 amount); -} diff --git a/packages/contracts/contracts/l2/staking/IL2StakingTypes.sol b/packages/contracts/contracts/l2/staking/IL2StakingTypes.sol deleted file mode 100644 index 500694e89..000000000 --- a/packages/contracts/contracts/l2/staking/IL2StakingTypes.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IL2StakingTypes { - /// @dev Message codes for the L1 -> L2 bridge callhook - enum L1MessageCodes { - RECEIVE_INDEXER_STAKE_CODE, - RECEIVE_DELEGATION_CODE - } - - /// @dev Encoded message struct when receiving indexer stake through the bridge - struct ReceiveIndexerStakeData { - address indexer; - } - - /// @dev Encoded message struct when receiving delegation through the bridge - struct ReceiveDelegationData { - address indexer; - address delegator; - } -} diff --git a/packages/contracts/contracts/l2/staking/L2Staking.sol b/packages/contracts/contracts/l2/staking/L2Staking.sol index 278e26a50..305747801 100644 --- a/packages/contracts/contracts/l2/staking/L2Staking.sol +++ b/packages/contracts/contracts/l2/staking/L2Staking.sol @@ -3,16 +3,23 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { Staking } from "../../staking/Staking.sol"; -import { IL2StakingBase } from "./IL2StakingBase.sol"; +import { IL2StakingBase } from "@graphprotocol/interfaces/contracts/contracts/l2/staking/IL2StakingBase.sol"; import { Stakes } from "../../staking/libs/Stakes.sol"; -import { IStakes } from "../../staking/libs/IStakes.sol"; -import { IL2StakingTypes } from "./IL2StakingTypes.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; +import { IL2StakingTypes } from "@graphprotocol/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol"; + +// solhint-disable-next-line no-unused-import +import { ICallhookReceiver } from "@graphprotocol/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol"; // Used by @inheritdoc /** * @title L2Staking contract - * @dev This contract is the L2 variant of the Staking contract. It adds a function + * @author Edge & Node + * @notice This contract is the L2 variant of the Staking contract. It adds a function * to receive an indexer's stake or delegation from L1. Note that this contract inherits Staking, * which uses a StakingExtension contract to implement the full IStaking interface through delegatecalls. */ @@ -24,10 +31,14 @@ contract L2Staking is Staking, IL2StakingBase { uint256 private constant MINIMUM_DELEGATION = 1e18; /** - * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * @notice Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator * gets `shares` for the delegation pool proportionally to the tokens staked. * This is copied from IStakingExtension, but we can't inherit from it because we * don't implement the full interface here. + * @param indexer Address of the indexer receiving the delegation + * @param delegator Address of the delegator + * @param tokens Amount of tokens delegated + * @param shares Amount of shares issued to the delegator */ event StakeDelegated(address indexed indexer, address indexed delegator, uint256 tokens, uint256 shares); @@ -48,7 +59,7 @@ contract L2Staking is Staking, IL2StakingBase { } /** - * @notice Receive tokens with a callhook from the bridge. + * @inheritdoc ICallhookReceiver * @dev The encoded _data can contain information about an indexer's stake * or a delegator's delegation. * See L1MessageCodes in IL2Staking for the supported messages. @@ -82,7 +93,7 @@ contract L2Staking is Staking, IL2StakingBase { } /** - * @dev Receive an Indexer's stake from L1. + * @notice Receive an Indexer's stake from L1. * The specified amount is added to the indexer's stake; the indexer's * address is specified in the _indexerData struct. * @param _amount Amount of tokens that were transferred @@ -105,7 +116,7 @@ contract L2Staking is Staking, IL2StakingBase { } /** - * @dev Receive a Delegator's delegation from L1. + * @notice Receive a Delegator's delegation from L1. * The specified amount is added to the delegator's delegation; the delegator's * address and the indexer's address are specified in the _delegationData struct. * Note that no delegation tax is applied here. diff --git a/packages/contracts/contracts/l2/token/GraphTokenUpgradeable.sol b/packages/contracts/contracts/l2/token/GraphTokenUpgradeable.sol index 0f5cf0ecb..d26371533 100644 --- a/packages/contracts/contracts/l2/token/GraphTokenUpgradeable.sol +++ b/packages/contracts/contracts/l2/token/GraphTokenUpgradeable.sol @@ -2,6 +2,10 @@ pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, gas-small-strings, gas-strict-inequalities +// solhint-disable named-parameters-mapping + import { ERC20BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20BurnableUpgradeable.sol"; import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/cryptography/ECDSAUpgradeable.sol"; @@ -10,7 +14,8 @@ import { Governed } from "../../governance/Governed.sol"; /** * @title GraphTokenUpgradeable contract - * @dev This is the implementation of the ERC20 Graph Token. + * @author Edge & Node + * @notice This is the implementation of the ERC20 Graph Token. * The implementation exposes a permit() function to allow for a spender to send a signed message * and approve funds to a spender following EIP2612 to make integration with other contracts easier. * @@ -47,16 +52,23 @@ abstract contract GraphTokenUpgradeable is GraphUpgradeable, Governed, ERC20Burn bytes32 private DOMAIN_SEPARATOR; // solhint-disable-line var-name-mixedcase /// @dev Addresses for which this mapping is true are allowed to mint tokens mapping(address => bool) private _minters; - /// Nonces for permit signatures for each token holder + /// @notice Nonces for permit signatures for each token holder mapping(address => uint256) public nonces; /// @dev Storage gap added in case we need to add state variables to this contract uint256[47] private __gap; // -- Events -- - /// Emitted when a new minter is added + /** + * @notice Emitted when a new minter is added + * @param account Address of the minter that was added + */ event MinterAdded(address indexed account); - /// Emitted when a minter is removed + + /** + * @notice Emitted when a minter is removed + * @param account Address of the minter that was removed + */ event MinterRemoved(address indexed account); /// @dev Reverts if the caller is not an authorized minter @@ -145,7 +157,7 @@ abstract contract GraphTokenUpgradeable is GraphUpgradeable, Governed, ERC20Burn } /** - * @dev Graph Token Contract initializer. + * @notice Graph Token Contract initializer. * @param _owner Owner of this contract, who will hold the initial supply and will be a minter * @param _initialSupply Initial supply of GRT */ @@ -173,7 +185,7 @@ abstract contract GraphTokenUpgradeable is GraphUpgradeable, Governed, ERC20Burn } /** - * @dev Add a new minter. + * @notice Add a new minter. * @param _account Address of the minter */ function _addMinter(address _account) private { @@ -182,7 +194,7 @@ abstract contract GraphTokenUpgradeable is GraphUpgradeable, Governed, ERC20Burn } /** - * @dev Remove a minter. + * @notice Remove a minter. * @param _account Address of the minter */ function _removeMinter(address _account) private { @@ -191,7 +203,7 @@ abstract contract GraphTokenUpgradeable is GraphUpgradeable, Governed, ERC20Burn } /** - * @dev Get the running network chain ID. + * @notice Get the running network chain ID. * @return The chain ID */ function _getChainID() private pure returns (uint256) { diff --git a/packages/contracts/contracts/l2/token/L2GraphToken.sol b/packages/contracts/contracts/l2/token/L2GraphToken.sol index 639444870..d37731f53 100644 --- a/packages/contracts/contracts/l2/token/L2GraphToken.sol +++ b/packages/contracts/contracts/l2/token/L2GraphToken.sol @@ -2,27 +2,48 @@ pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { GraphTokenUpgradeable } from "./GraphTokenUpgradeable.sol"; -import { IArbToken } from "../../arbitrum/IArbToken.sol"; +import { IArbToken } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IArbToken.sol"; /** * @title L2 Graph Token Contract - * @dev Provides the L2 version of the GRT token, meant to be minted/burned + * @author Edge & Node + * @notice Provides the L2 version of the GRT token, meant to be minted/burned * through the L2GraphTokenGateway. */ contract L2GraphToken is GraphTokenUpgradeable, IArbToken { - /// Address of the gateway (on L2) that is allowed to mint tokens + /// @notice Address of the gateway (on L2) that is allowed to mint tokens address public gateway; - /// Address of the corresponding Graph Token contract on L1 + /// @notice Address of the corresponding Graph Token contract on L1 address public override l1Address; - /// Emitted when the bridge / gateway has minted new tokens, i.e. tokens were transferred to L2 + /** + * @notice Emitted when the bridge / gateway has minted new tokens, i.e. tokens were transferred to L2 + * @param account Address that received the minted tokens + * @param amount Amount of tokens minted + */ event BridgeMinted(address indexed account, uint256 amount); - /// Emitted when the bridge / gateway has burned tokens, i.e. tokens were transferred back to L1 + + /** + * @notice Emitted when the bridge / gateway has burned tokens, i.e. tokens were transferred back to L1 + * @param account Address from which tokens were burned + * @param amount Amount of tokens burned + */ event BridgeBurned(address indexed account, uint256 amount); - /// Emitted when the address of the gateway has been updated + + /** + * @notice Emitted when the address of the gateway has been updated + * @param gateway Address of the new gateway + */ event GatewaySet(address gateway); - /// Emitted when the address of the Graph Token contract on L1 has been updated + + /** + * @notice Emitted when the address of the Graph Token contract on L1 has been updated + * @param l1Address Address of the L1 Graph Token contract + */ event L1AddressSet(address l1Address); /** @@ -69,9 +90,8 @@ contract L2GraphToken is GraphTokenUpgradeable, IArbToken { } /** - * @notice Increases token supply, only callable by the L1/L2 bridge (when tokens are transferred to L2) - * @param _account Address to credit with the new tokens - * @param _amount Number of tokens to mint + * @inheritdoc IArbToken + * @dev Only callable by the L2GraphTokenGateway when tokens are transferred to L2 */ function bridgeMint(address _account, uint256 _amount) external override onlyGateway { _mint(_account, _amount); @@ -79,9 +99,8 @@ contract L2GraphToken is GraphTokenUpgradeable, IArbToken { } /** - * @notice Decreases token supply, only callable by the L1/L2 bridge (when tokens are transferred to L1). - * @param _account Address from which to extract the tokens - * @param _amount Number of tokens to burn + * @inheritdoc IArbToken + * @dev Only callable by the L2GraphTokenGateway when tokens are transferred back to L1 */ function bridgeBurn(address _account, uint256 _amount) external override onlyGateway { burnFrom(_account, _amount); diff --git a/packages/contracts/contracts/libraries/Base58Encoder.sol b/packages/contracts/contracts/libraries/Base58Encoder.sol index 9af197855..91caa8855 100644 --- a/packages/contracts/contracts/libraries/Base58Encoder.sol +++ b/packages/contracts/contracts/libraries/Base58Encoder.sol @@ -2,14 +2,25 @@ pragma solidity ^0.7.6; -/// @title Base58Encoder -/// @author Original author - Martin Lundfall (martin.lundfall@gmail.com) -/// Based on https://github.com/MrChico/verifyIPFS +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one + +/** + * @title Base58Encoder + * @author Original author - Martin Lundfall (martin.lundfall@gmail.com) + * @notice Library for encoding bytes to Base58 format, used for IPFS hashes + * @dev Based on https://github.com/MrChico/verifyIPFS + */ library Base58Encoder { - bytes constant sha256MultiHash = hex"1220"; - bytes constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + /// @dev SHA-256 multihash prefix for IPFS hashes + // solhint-disable-next-line const-name-snakecase + bytes internal constant sha256MultiHash = hex"1220"; + /// @dev Base58 alphabet used for encoding + bytes internal constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - /// @dev Converts hex string to base 58 + /// @notice Converts hex string to base 58 + /// @param source The bytes to encode + /// @return The base58 encoded bytes function encode(bytes memory source) internal pure returns (bytes memory) { if (source.length == 0) return new bytes(0); uint8[] memory digits = new uint8[](64); @@ -32,6 +43,12 @@ library Base58Encoder { return toAlphabet(reverse(truncate(digits, digitlength))); } + /** + * @notice Truncate an array to a specific length + * @param array The array to truncate + * @param length The desired length + * @return The truncated array + */ function truncate(uint8[] memory array, uint8 length) internal pure returns (uint8[] memory) { uint8[] memory output = new uint8[](length); for (uint256 i = 0; i < length; i++) { @@ -40,6 +57,11 @@ library Base58Encoder { return output; } + /** + * @notice Reverse an array + * @param input The array to reverse + * @return The reversed array + */ function reverse(uint8[] memory input) internal pure returns (uint8[] memory) { uint8[] memory output = new uint8[](input.length); for (uint256 i = 0; i < input.length; i++) { @@ -48,6 +70,11 @@ library Base58Encoder { return output; } + /** + * @notice Convert indices to alphabet characters + * @param indices The indices to convert + * @return The alphabet characters as bytes + */ function toAlphabet(uint8[] memory indices) internal pure returns (bytes memory) { bytes memory output = new bytes(indices.length); for (uint256 i = 0; i < indices.length; i++) { diff --git a/packages/contracts/contracts/libraries/HexStrings.sol b/packages/contracts/contracts/libraries/HexStrings.sol index 4842883a9..2b5e314e6 100644 --- a/packages/contracts/contracts/libraries/HexStrings.sol +++ b/packages/contracts/contracts/libraries/HexStrings.sol @@ -2,12 +2,22 @@ pragma solidity ^0.7.6; -/// @title HexStrings -/// Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8dd744fc1843d285c38e54e9d439dea7f6b93495/contracts/utils/Strings.sol +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one + +/** + * @title HexStrings + * @author Edge & Node + * @notice Library for converting values to hexadecimal string representations + * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8dd744fc1843d285c38e54e9d439dea7f6b93495/contracts/utils/Strings.sol + */ library HexStrings { + /// @dev Hexadecimal symbols used for string conversion bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - /// @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + /// @notice Converts a `uint256` to its ASCII `string` hexadecimal representation. + /// @param value The uint256 value to convert + /// @return The hexadecimal string representation function toString(uint256 value) internal pure returns (string memory) { if (value == 0) { return "0x00"; @@ -21,7 +31,10 @@ library HexStrings { return toHexString(value, length); } - /// @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + /// @notice Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + /// @param value The uint256 value to convert + /// @param length The fixed length of the output string + /// @return The hexadecimal string representation with fixed length function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; diff --git a/packages/contracts/contracts/payments/AllocationExchange.sol b/packages/contracts/contracts/payments/AllocationExchange.sol index 5f0b30b44..288bdda32 100644 --- a/packages/contracts/contracts/payments/AllocationExchange.sol +++ b/packages/contracts/contracts/payments/AllocationExchange.sol @@ -3,16 +3,20 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "@openzeppelin/contracts/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-calldata-parameters, gas-increment-by-one, gas-indexed-events, gas-small-strings +// solhint-disable named-parameters-mapping -import "../governance/Governed.sol"; -import "../staking/IStaking.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { Governed } from "../governance/Governed.sol"; +import { IStaking } from "@graphprotocol/interfaces/contracts/contracts/staking/IStaking.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; /** * @title Allocation Exchange - * @dev This contract holds tokens that anyone with a voucher signed by the + * @author Edge & Node + * @notice This contract holds tokens that anyone with a voucher signed by the * authority can redeem. The contract validates if the voucher presented is valid * and then sends tokens to the Staking contract by calling the collect() function * passing the voucher allocationID. The contract enforces that only one voucher for @@ -20,9 +24,14 @@ import { IGraphToken } from "../token/IGraphToken.sol"; * Only governance can change the authority. */ contract AllocationExchange is Governed { - // An allocation voucher represents a signed message that allows - // redeeming an amount of funds from this contract and collect - // them as part of an allocation + /** + * @dev An allocation voucher represents a signed message that allows + * redeeming an amount of funds from this contract and collect + * them as part of an allocation + * @param allocationID Address of the allocation + * @param amount Amount of tokens to redeem + * @param signature Signature from the authority (65 bytes) + */ struct AllocationVoucher { address allocationID; uint256 amount; @@ -31,20 +40,43 @@ contract AllocationExchange is Governed { // -- Constants -- + /// @dev Maximum uint256 value used for unlimited token approvals uint256 private constant MAX_UINT256 = 2 ** 256 - 1; + /// @dev Expected length of ECDSA signatures uint256 private constant SIGNATURE_LENGTH = 65; // -- State -- - IStaking private immutable staking; - IGraphToken private immutable graphToken; + /// @dev Reference to the Staking contract + IStaking private immutable STAKING; + /// @dev Reference to the Graph Token contract + IGraphToken private immutable GRAPH_TOKEN; + /// @notice Mapping of authorized accounts that can redeem allocations mapping(address => bool) public authority; + /// @notice Mapping of allocations that have been redeemed mapping(address => bool) public allocationsRedeemed; // -- Events + /** + * @notice Emitted when an authority is set or unset + * @param account Address of the authority + * @param authorized Whether the authority is authorized + */ event AuthoritySet(address indexed account, bool authorized); + + /** + * @notice Emitted when an allocation voucher is redeemed + * @param allocationID Address of the allocation + * @param amount Amount of tokens redeemed + */ event AllocationRedeemed(address indexed allocationID, uint256 amount); + + /** + * @notice Emitted when tokens are withdrawn from the contract + * @param to Address that received the tokens + * @param amount Amount of tokens withdrawn + */ event TokensWithdrawn(address indexed to, uint256 amount); // -- Functions @@ -60,8 +92,8 @@ contract AllocationExchange is Governed { require(_governor != address(0), "Exchange: governor must be set"); Governed._initialize(_governor); - graphToken = _graphToken; - staking = _staking; + GRAPH_TOKEN = _graphToken; + STAKING = _staking; _setAuthority(_authority, true); } @@ -70,7 +102,7 @@ contract AllocationExchange is Governed { * @dev Increased gas efficiency instead of approving on each voucher redeem */ function approveAll() external { - graphToken.approve(address(staking), MAX_UINT256); + GRAPH_TOKEN.approve(address(STAKING), MAX_UINT256); } /** @@ -82,7 +114,7 @@ contract AllocationExchange is Governed { function withdraw(address _to, uint256 _amount) external onlyGovernor { require(_to != address(0), "Exchange: empty destination"); require(_amount != 0, "Exchange: empty amount"); - require(graphToken.transfer(_to, _amount), "Exchange: cannot transfer"); + require(GRAPH_TOKEN.transfer(_to, _amount), "Exchange: cannot transfer"); emit TokensWithdrawn(_to, _amount); } @@ -155,7 +187,7 @@ contract AllocationExchange is Governed { // Make the staking contract collect funds from this contract // The Staking contract will validate if the allocation is valid - staking.collect(_voucher.amount, _voucher.allocationID); + STAKING.collect(_voucher.amount, _voucher.allocationID); emit AllocationRedeemed(_voucher.allocationID, _voucher.amount); } diff --git a/packages/contracts/contracts/rewards/IRewardsIssuer.sol b/packages/contracts/contracts/rewards/IRewardsIssuer.sol deleted file mode 100644 index d50410b33..000000000 --- a/packages/contracts/contracts/rewards/IRewardsIssuer.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IRewardsIssuer { - /** - * @dev Get allocation data to calculate rewards issuance - * - * @param allocationId The allocation Id - * @return isActive Whether the allocation is active or not - * @return indexer The indexer address - * @return subgraphDeploymentId Subgraph deployment id for the allocation - * @return tokens Amount of allocated tokens - * @return accRewardsPerAllocatedToken Rewards snapshot - * @return accRewardsPending Snapshot of accumulated rewards from previous allocation resizing, pending to be claimed - */ - function getAllocationData( - address allocationId - ) - external - view - returns ( - bool isActive, - address indexer, - bytes32 subgraphDeploymentId, - uint256 tokens, - uint256 accRewardsPerAllocatedToken, - uint256 accRewardsPending - ); - - /** - * @notice Return the total amount of tokens allocated to subgraph. - * @param _subgraphDeploymentId Deployment Id for the subgraph - * @return Total tokens allocated to subgraph - */ - function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentId) external view returns (uint256); -} diff --git a/packages/contracts/contracts/rewards/IRewardsManager.sol b/packages/contracts/contracts/rewards/IRewardsManager.sol deleted file mode 100644 index b31064d1b..000000000 --- a/packages/contracts/contracts/rewards/IRewardsManager.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IRewardsManager { - /** - * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. - */ - struct Subgraph { - uint256 accRewardsForSubgraph; - uint256 accRewardsForSubgraphSnapshot; - uint256 accRewardsPerSignalSnapshot; - uint256 accRewardsPerAllocatedToken; - } - - // -- Config -- - - function setIssuancePerBlock(uint256 _issuancePerBlock) external; - - function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; - - function setSubgraphService(address _subgraphService) external; - - // -- Denylist -- - - function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external; - - function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external; - - function isDenied(bytes32 _subgraphDeploymentID) external view returns (bool); - - // -- Getters -- - - function getNewRewardsPerSignal() external view returns (uint256); - - function getAccRewardsPerSignal() external view returns (uint256); - - function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) external view returns (uint256); - - function getAccRewardsPerAllocatedToken(bytes32 _subgraphDeploymentID) external view returns (uint256, uint256); - - function getRewards(address _rewardsIssuer, address _allocationID) external view returns (uint256); - - function calcRewards(uint256 _tokens, uint256 _accRewardsPerAllocatedToken) external pure returns (uint256); - - // -- Updates -- - - function updateAccRewardsPerSignal() external returns (uint256); - - function takeRewards(address _allocationID) external returns (uint256); - - // -- Hooks -- - - function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); - - function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) external returns (uint256); -} diff --git a/packages/contracts/contracts/rewards/RewardsManager.sol b/packages/contracts/contracts/rewards/RewardsManager.sol index 58f654b91..458893308 100644 --- a/packages/contracts/contracts/rewards/RewardsManager.sol +++ b/packages/contracts/contracts/rewards/RewardsManager.sol @@ -3,25 +3,38 @@ pragma solidity 0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, gas-indexed-events, gas-small-strings, gas-strict-inequalities + import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol"; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; import { Managed } from "../governance/Managed.sol"; import { MathUtils } from "../staking/libs/MathUtils.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; -import { RewardsManagerV5Storage } from "./RewardsManagerStorage.sol"; -import { IRewardsManager } from "./IRewardsManager.sol"; -import { IRewardsIssuer } from "./IRewardsIssuer.sol"; +import { RewardsManagerV6Storage } from "./RewardsManagerStorage.sol"; +import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; +import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; +import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol"; /** * @title Rewards Manager Contract + * @author Edge & Node + * @notice Manages rewards distribution for indexers and delegators in the Graph Protocol * @dev Tracks how inflationary GRT rewards should be handed out. Relies on the Curation contract * and the Staking contract. Signaled GRT in Curation determine what percentage of the tokens go * towards each subgraph. Then each Subgraph can have multiple Indexers Staked on it. Thus, the * total rewards for the Subgraph are split up for each Indexer based on much they have Staked on * that Subgraph. * + * @dev If an `issuanceAllocator` is set, it is used to determine the amount of GRT to be issued per block. + * Otherwise, the `issuancePerBlock` variable is used. In relation to the IssuanceAllocator, this contract + * is a self-minting target responsible for directly minting allocated GRT. + * * Note: * The contract provides getter functions to query the state of accrued rewards: * - getAccRewardsPerSignal @@ -30,8 +43,9 @@ import { IRewardsIssuer } from "./IRewardsIssuer.sol"; * - getRewards * These functions may overestimate the actual rewards due to changes in the total supply * until the actual takeRewards function is called. + * custom:security-contact Please email security+contracts@ thegraph.com (remove space) if you find any bugs. We might have an active bug bounty program. */ -contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsManager { +contract RewardsManager is RewardsManagerV6Storage, GraphUpgradeable, IERC165, IRewardsManager, IIssuanceTarget { using SafeMath for uint256; /// @dev Fixed point scaling factor used for decimals in reward calculations @@ -40,30 +54,60 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa // -- Events -- /** - * @dev Emitted when rewards are assigned to an indexer. + * @notice Emitted when rewards are assigned to an indexer. * @dev We use the Horizon prefix to change the event signature which makes network subgraph development much easier + * @param indexer Address of the indexer receiving rewards + * @param allocationID Address of the allocation receiving rewards + * @param amount Amount of rewards assigned */ event HorizonRewardsAssigned(address indexed indexer, address indexed allocationID, uint256 amount); /** - * @dev Emitted when rewards are denied to an indexer + * @notice Emitted when rewards are denied to an indexer * @param indexer Address of the indexer being denied rewards * @param allocationID Address of the allocation being denied rewards */ event RewardsDenied(address indexed indexer, address indexed allocationID); /** - * @dev Emitted when a subgraph is denied for claiming rewards + * @notice Emitted when rewards are denied to an indexer due to eligibility + * @param indexer Address of the indexer being denied rewards + * @param allocationID Address of the allocation being denied rewards + * @param amount Amount of rewards that would have been assigned + */ + event RewardsDeniedDueToEligibility(address indexed indexer, address indexed allocationID, uint256 amount); + + /** + * @notice Emitted when a subgraph is denied for claiming rewards * @param subgraphDeploymentID Subgraph deployment ID being denied * @param sinceBlock Block number since when the subgraph is denied */ event RewardsDenylistUpdated(bytes32 indexed subgraphDeploymentID, uint256 sinceBlock); /** - * @dev Emitted when the subgraph service is set + * @notice Emitted when the subgraph service is set + * @param oldSubgraphService Previous subgraph service address + * @param newSubgraphService New subgraph service address */ event SubgraphServiceSet(address indexed oldSubgraphService, address indexed newSubgraphService); + /** + * @notice Emitted when the issuance allocator is set + * @param oldIssuanceAllocator Previous issuance allocator address + * @param newIssuanceAllocator New issuance allocator address + */ + event IssuanceAllocatorSet(address indexed oldIssuanceAllocator, address indexed newIssuanceAllocator); + + /** + * @notice Emitted when the rewards eligibility oracle contract is set + * @param oldRewardsEligibilityOracle Previous rewards eligibility oracle address + * @param newRewardsEligibilityOracle New rewards eligibility oracle address + */ + event RewardsEligibilityOracleSet( + address indexed oldRewardsEligibilityOracle, + address indexed newRewardsEligibilityOracle + ); + // -- Modifiers -- /** @@ -82,23 +126,38 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa Managed._initialize(_controller); } + /** + * @inheritdoc IERC165 + * @dev Implements ERC165 interface detection + * Returns true if this contract implements the interface defined by interfaceId. + * See: https://eips.ethereum.org/EIPS/eip-165 + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC165).interfaceId || + interfaceId == type(IIssuanceTarget).interfaceId || + interfaceId == type(IRewardsManager).interfaceId; + } + // -- Config -- /** - * @dev Sets the GRT issuance per block. + * @inheritdoc IRewardsManager + * @dev When an IssuanceAllocator is set, the effective issuance will be determined by the allocator, + * but this local value can still be updated for cases when the allocator is later removed. + * * The issuance is defined as a fixed amount of rewards per block in GRT. * Whenever this function is called in layer 2, the updateL2MintAllowance function * _must_ be called on the L1GraphTokenGateway in L1, to ensure the bridge can mint the * right amount of tokens. - * @param _issuancePerBlock Issuance expressed in GRT per block (scaled by 1e18) */ function setIssuancePerBlock(uint256 _issuancePerBlock) external override onlyGovernor { _setIssuancePerBlock(_issuancePerBlock); } /** - * @dev Sets the GRT issuance per block. - * The issuance is defined as a fixed amount of rewards per block in GRT. + * @notice Sets the GRT issuance per block. + * @dev The issuance is defined as a fixed amount of rewards per block in GRT. * @param _issuancePerBlock Issuance expressed in GRT per block (scaled by 1e18) */ function _setIssuancePerBlock(uint256 _issuancePerBlock) private { @@ -110,8 +169,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @notice Sets the subgraph oracle allowed to deny distribution of rewards to subgraphs - * @param _subgraphAvailabilityOracle Address of the subgraph availability oracle + * @inheritdoc IRewardsManager */ function setSubgraphAvailabilityOracle(address _subgraphAvailabilityOracle) external override onlyGovernor { subgraphAvailabilityOracle = _subgraphAvailabilityOracle; @@ -119,7 +177,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @notice Sets the minimum signaled tokens on a subgraph to start accruing rewards + * @inheritdoc IRewardsManager * @dev Can be set to zero which means that this feature is not being used * @param _minimumSubgraphSignal Minimum signaled tokens */ @@ -133,26 +191,91 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa emit ParameterUpdated("minimumSubgraphSignal"); } + /** + * @inheritdoc IRewardsManager + */ function setSubgraphService(address _subgraphService) external override onlyGovernor { address oldSubgraphService = address(subgraphService); subgraphService = IRewardsIssuer(_subgraphService); emit SubgraphServiceSet(oldSubgraphService, _subgraphService); } + /** + * @inheritdoc IIssuanceTarget + * @dev This function facilitates upgrades by providing a standard way for targets + * to change their allocator. Only the governor can call this function. + * Note that the IssuanceAllocator can be set to the zero address to disable use of an allocator, and + * use the local `issuancePerBlock` variable instead to control issuance. + */ + function setIssuanceAllocator(address newIssuanceAllocator) external override onlyGovernor { + if (address(issuanceAllocator) != newIssuanceAllocator) { + // Update rewards calculation before changing the issuance allocator + updateAccRewardsPerSignal(); + + // Check that the contract supports the IIssuanceAllocationDistribution interface + // Allow zero address to disable the allocator + if (newIssuanceAllocator != address(0)) { + require( + IERC165(newIssuanceAllocator).supportsInterface(type(IIssuanceAllocationDistribution).interfaceId), + "Contract does not support IIssuanceAllocationDistribution interface" + ); + } + + address oldIssuanceAllocator = address(issuanceAllocator); + issuanceAllocator = IIssuanceAllocationDistribution(newIssuanceAllocator); + emit IssuanceAllocatorSet(oldIssuanceAllocator, newIssuanceAllocator); + } + } + + /** + * @inheritdoc IIssuanceTarget + * @dev Ensures that all reward calculations are up-to-date with the current block + * before any allocation changes take effect. + * + * This function can be called by anyone to update the rewards calculation state. + * The IssuanceAllocator calls this function before changing a target's allocation to ensure + * all issuance is properly accounted for with the current issuance rate before applying an + * issuance allocation change. + */ + function beforeIssuanceAllocationChange() external override { + // Update rewards calculation with the current issuance rate + updateAccRewardsPerSignal(); + } + + /** + * @inheritdoc IRewardsManager + * @dev Note that the rewards eligibility oracle can be set to the zero address to disable use of an oracle, in + * which case no indexers will be denied rewards due to eligibility. + */ + function setRewardsEligibilityOracle(address newRewardsEligibilityOracle) external override onlyGovernor { + if (address(rewardsEligibilityOracle) != newRewardsEligibilityOracle) { + // Check that the contract supports the IRewardsEligibility interface + // Allow zero address to disable the oracle + if (newRewardsEligibilityOracle != address(0)) { + require( + IERC165(newRewardsEligibilityOracle).supportsInterface(type(IRewardsEligibility).interfaceId), + "Contract does not support IRewardsEligibility interface" + ); + } + + address oldRewardsEligibilityOracle = address(rewardsEligibilityOracle); + rewardsEligibilityOracle = IRewardsEligibility(newRewardsEligibilityOracle); + emit RewardsEligibilityOracleSet(oldRewardsEligibilityOracle, newRewardsEligibilityOracle); + } + } + // -- Denylist -- /** - * @notice Denies to claim rewards for a subgraph + * @inheritdoc IRewardsManager * @dev Can only be called by the subgraph availability oracle - * @param _subgraphDeploymentID Subgraph deployment ID - * @param _deny Whether to set the subgraph as denied for claiming rewards or not */ function setDenied(bytes32 _subgraphDeploymentID, bool _deny) external override onlySubgraphAvailabilityOracle { _setDenied(_subgraphDeploymentID, _deny); } /** - * @dev Internal: Denies to claim rewards for a subgraph. + * @notice Internal: Denies to claim rewards for a subgraph. * @param _subgraphDeploymentID Subgraph deployment ID * @param _deny Whether to set the subgraph as denied for claiming rewards or not */ @@ -162,11 +285,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa emit RewardsDenylistUpdated(_subgraphDeploymentID, sinceBlock); } - /** - * @notice Tells if subgraph is in deny list - * @param _subgraphDeploymentID Subgraph deployment ID to check - * @return Whether the subgraph is denied for claiming rewards or not - */ + /// @inheritdoc IRewardsManager function isDenied(bytes32 _subgraphDeploymentID) public view override returns (bool) { return denylist[_subgraphDeploymentID] > 0; } @@ -174,7 +293,18 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa // -- Getters -- /** - * @notice Gets the issuance of rewards per signal since last updated + * @inheritdoc IRewardsManager + * @dev Gets the effective issuance per block, taking into account the IssuanceAllocator if set + */ + function getRewardsIssuancePerBlock() public view override returns (uint256) { + if (address(issuanceAllocator) != address(0)) { + return issuanceAllocator.getTargetIssuancePerBlock(address(this)).selfIssuancePerBlock; + } + return issuancePerBlock; + } + + /** + * @inheritdoc IRewardsManager * @dev Linear formula: `x = r * t` * * Notation: @@ -190,8 +320,10 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa if (t == 0) { return 0; } - // ...or if issuance is zero - if (issuancePerBlock == 0) { + + uint256 rewardsIssuancePerBlock = getRewardsIssuancePerBlock(); + + if (rewardsIssuancePerBlock == 0) { return 0; } @@ -202,26 +334,19 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa return 0; } - uint256 x = issuancePerBlock.mul(t); + uint256 x = rewardsIssuancePerBlock.mul(t); // Get the new issuance per signalled token // We multiply the decimals to keep the precision as fixed-point number return x.mul(FIXED_POINT_SCALING_FACTOR).div(signalledTokens); } - /** - * @notice Gets the currently accumulated rewards per signal - * @return Currently accumulated rewards per signal - */ + /// @inheritdoc IRewardsManager function getAccRewardsPerSignal() public view override returns (uint256) { return accRewardsPerSignal.add(getNewRewardsPerSignal()); } - /** - * @notice Gets the accumulated rewards for the subgraph - * @param _subgraphDeploymentID Subgraph deployment - * @return Accumulated rewards for subgraph - */ + /// @inheritdoc IRewardsManager function getAccRewardsForSubgraph(bytes32 _subgraphDeploymentID) public view override returns (uint256) { Subgraph storage subgraph = subgraphs[_subgraphDeploymentID]; @@ -237,12 +362,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa return subgraph.accRewardsForSubgraph.add(newRewards); } - /** - * @notice Gets the accumulated rewards per allocated token for the subgraph - * @param _subgraphDeploymentID Subgraph deployment - * @return Accumulated rewards per allocated token for the subgraph - * @return Accumulated rewards for subgraph - */ + /// @inheritdoc IRewardsManager function getAccRewardsPerAllocatedToken( bytes32 _subgraphDeploymentID ) public view override returns (uint256, uint256) { @@ -280,10 +400,9 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa // -- Updates -- /** - * @notice Updates the accumulated rewards per signal and save checkpoint block number + * @inheritdoc IRewardsManager * @dev Must be called before `issuancePerBlock` or `total signalled GRT` changes. * Called from the Curation contract on mint() and burn() - * @return Accumulated rewards per signal */ function updateAccRewardsPerSignal() public override returns (uint256) { accRewardsPerSignal = getAccRewardsPerSignal(); @@ -292,11 +411,9 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @notice Triggers an update of rewards for a subgraph + * @inheritdoc IRewardsManager * @dev Must be called before `signalled GRT` on a subgraph changes. * Hook called from the Curation contract on mint() and burn() - * @param _subgraphDeploymentID Subgraph deployment - * @return Accumulated rewards for subgraph */ function onSubgraphSignalUpdate(bytes32 _subgraphDeploymentID) external override returns (uint256) { // Called since `total signalled GRT` will change @@ -310,12 +427,8 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @notice Triggers an update of rewards for a subgraph - * @dev Must be called before allocation on a subgraph changes. - * Hook called from the Staking contract on allocate() and close() - * - * @param _subgraphDeploymentID Subgraph deployment - * @return Accumulated rewards per allocated token for a subgraph + * @inheritdoc IRewardsManager + * @dev Hook called from the Staking contract on allocate() and close() */ function onSubgraphAllocationUpdate(bytes32 _subgraphDeploymentID) public override returns (uint256) { Subgraph storage subgraph = subgraphs[_subgraphDeploymentID]; @@ -327,13 +440,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa return subgraph.accRewardsPerAllocatedToken; } - /** - * @dev Calculate current rewards for a given allocation on demand. - * The allocation could be a legacy allocation or a new subgraph service allocation. - * Returns 0 if the allocation is not active. - * @param _allocationID Allocation - * @return Rewards amount for an allocation - */ + /// @inheritdoc IRewardsManager function getRewards(address _rewardsIssuer, address _allocationID) external view override returns (uint256) { require( _rewardsIssuer == address(staking()) || _rewardsIssuer == address(subgraphService), @@ -359,7 +466,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @dev Calculate rewards for a given accumulated rewards per allocated token. + * @notice Calculate rewards for a given accumulated rewards per allocated token * @param _tokens Tokens allocated * @param _accRewardsPerAllocatedToken Allocation accumulated rewards per token * @return Rewards amount @@ -372,7 +479,7 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @dev Calculate current rewards for a given allocation. + * @notice Calculate current rewards for a given allocation. * @param _tokens Tokens allocated * @param _startAccRewardsPerAllocatedToken Allocation start accumulated rewards * @param _endAccRewardsPerAllocatedToken Allocation end accumulated rewards @@ -388,13 +495,10 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa } /** - * @dev Pull rewards from the contract for a particular allocation. - * This function can only be called by an authorized rewards issuer which are + * @inheritdoc IRewardsManager + * @dev This function can only be called by an authorized rewards issuer which are * the staking contract (for legacy allocations), and the subgraph service (for new allocations). - * This function will mint the necessary tokens to reward based on the inflation calculation. * Mints 0 tokens if the allocation is not active. - * @param _allocationID Allocation - * @return Assigned rewards amount */ function takeRewards(address _allocationID) external override returns (uint256) { address rewardsIssuer = msg.sender; @@ -426,6 +530,13 @@ contract RewardsManager is RewardsManagerV5Storage, GraphUpgradeable, IRewardsMa rewards = accRewardsPending.add( _calcRewards(tokens, accRewardsPerAllocatedToken, updatedAccRewardsPerAllocatedToken) ); + + // Do not reward if indexer is not eligible based on rewards eligibility + if (address(rewardsEligibilityOracle) != address(0) && !rewardsEligibilityOracle.isEligible(indexer)) { + emit RewardsDeniedDueToEligibility(indexer, _allocationID, rewards); + return 0; + } + if (rewards > 0) { // Mint directly to rewards issuer for the reward amount // The rewards issuer contract will do bookkeeping of the reward and diff --git a/packages/contracts/contracts/rewards/RewardsManagerStorage.sol b/packages/contracts/contracts/rewards/RewardsManagerStorage.sol index d2ffa2b42..63897f431 100644 --- a/packages/contracts/contracts/rewards/RewardsManagerStorage.sol +++ b/packages/contracts/contracts/rewards/RewardsManagerStorage.sol @@ -1,44 +1,93 @@ // SPDX-License-Identifier: GPL-2.0-or-later +/* solhint-disable one-contract-per-file */ + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6 || 0.8.27; -import { IRewardsIssuer } from "./IRewardsIssuer.sol"; -import { IRewardsManager } from "./IRewardsManager.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; +import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol"; +import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; import { Managed } from "../governance/Managed.sol"; +/** + * @title RewardsManagerV1Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V1 + */ contract RewardsManagerV1Storage is Managed { // -- State -- + /// @dev Deprecated issuance rate variable (no longer used) uint256 private __DEPRECATED_issuanceRate; // solhint-disable-line var-name-mixedcase + /// @notice Accumulated rewards per signal uint256 public accRewardsPerSignal; + /// @notice Block number when accumulated rewards per signal was last updated uint256 public accRewardsPerSignalLastBlockUpdated; - // Address of role allowed to deny rewards on subgraphs + /// @notice Address of role allowed to deny rewards on subgraphs address public subgraphAvailabilityOracle; - // Subgraph related rewards: subgraph deployment ID => subgraph rewards + /// @notice Subgraph related rewards: subgraph deployment ID => subgraph rewards mapping(bytes32 => IRewardsManager.Subgraph) public subgraphs; - // Subgraph denylist : subgraph deployment ID => block when added or zero (if not denied) + /// @notice Subgraph denylist: subgraph deployment ID => block when added or zero (if not denied) mapping(bytes32 => uint256) public denylist; } +/** + * @title RewardsManagerV2Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V2 + */ contract RewardsManagerV2Storage is RewardsManagerV1Storage { - // Minimum amount of signaled tokens on a subgraph required to accrue rewards + /// @notice Minimum amount of signaled tokens on a subgraph required to accrue rewards uint256 public minimumSubgraphSignal; } +/** + * @title RewardsManagerV3Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V3 + */ contract RewardsManagerV3Storage is RewardsManagerV2Storage { - // Snapshot of the total supply of GRT when accRewardsPerSignal was last updated + /// @dev Deprecated token supply snapshot variable (no longer used) uint256 private __DEPRECATED_tokenSupplySnapshot; // solhint-disable-line var-name-mixedcase } +/** + * @title RewardsManagerV4Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V4 + */ contract RewardsManagerV4Storage is RewardsManagerV3Storage { - // GRT issued for indexer rewards per block + /// @notice GRT issued for indexer rewards per block + /// @dev Only used when issuanceAllocator is zero address. uint256 public issuancePerBlock; } +/** + * @title RewardsManagerV5Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V5 + */ contract RewardsManagerV5Storage is RewardsManagerV4Storage { - // Address of the subgraph service + /// @notice Address of the subgraph service IRewardsIssuer public subgraphService; } + +/** + * @title RewardsManagerV6Storage + * @author Edge & Node + * @notice Storage layout for RewardsManager V6 + * Includes support for Rewards Eligibility Oracle and Issuance Allocator. + */ +contract RewardsManagerV6Storage is RewardsManagerV5Storage { + /// @notice Address of the rewards eligibility oracle contract + IRewardsEligibility public rewardsEligibilityOracle; + /// @notice Address of the issuance allocator + IIssuanceAllocationDistribution public issuanceAllocator; +} diff --git a/packages/contracts/contracts/rewards/SubgraphAvailabilityManager.sol b/packages/contracts/contracts/rewards/SubgraphAvailabilityManager.sol index 3c0c98cd2..d24577b42 100644 --- a/packages/contracts/contracts/rewards/SubgraphAvailabilityManager.sol +++ b/packages/contracts/contracts/rewards/SubgraphAvailabilityManager.sol @@ -2,12 +2,17 @@ pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, gas-indexed-events, gas-small-strings, gas-strict-inequalities +// solhint-disable named-parameters-mapping + import { Governed } from "../governance/Governed.sol"; -import { IRewardsManager } from "./IRewardsManager.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; /** * @title Subgraph Availability Manager - * @dev Manages the availability of subgraphs by allowing oracles to vote on whether + * @author Edge & Node + * @notice Manages the availability of subgraphs by allowing oracles to vote on whether * a subgraph should be denied rewards or not. When enough oracles have voted to deny or * allow rewards for a subgraph, it calls the RewardsManager Contract to set the correct * state. The oracles and the execution threshold are set at deployment time. @@ -21,15 +26,16 @@ contract SubgraphAvailabilityManager is Governed { uint256 public constant NUM_ORACLES = 5; /// @notice Number of votes required to execute a deny or allow call to the RewardsManager - uint256 public immutable executionThreshold; + uint256 public immutable executionThreshold; // solhint-disable-line immutable-vars-naming /// @dev Address of the RewardsManager contract + // solhint-disable-next-line immutable-vars-naming IRewardsManager private immutable rewardsManager; // -- State -- - /// @dev Nonce for generating votes on subgraph deployment IDs. - /// Increased whenever oracles or voteTimeLimit change, to invalidate old votes. + /// @notice Nonce for generating votes on subgraph deployment IDs + /// @dev Increased whenever oracles or voteTimeLimit change, to invalidate old votes uint256 public currentNonce; /// @notice Time limit for a vote to be valid @@ -39,30 +45,30 @@ contract SubgraphAvailabilityManager is Governed { address[NUM_ORACLES] public oracles; /// @notice Mapping of current nonce to subgraph deployment ID to an array of timestamps of last deny vote - /// currentNonce => subgraphDeploymentId => timestamp[oracleIndex] + /// @dev currentNonce => subgraphDeploymentId => timestamp[oracleIndex] mapping(uint256 => mapping(bytes32 => uint256[NUM_ORACLES])) public lastDenyVote; - /// @notice Mapping of current nonce to subgraph deployment ID to an array of timestamp of last allow vote - /// currentNonce => subgraphDeploymentId => timestamp[oracleIndex] + /// @notice Mapping of current nonce to subgraph deployment ID to an array of timestamp of last allow vote + /// @dev currentNonce => subgraphDeploymentId => timestamp[oracleIndex] mapping(uint256 => mapping(bytes32 => uint256[NUM_ORACLES])) public lastAllowVote; // -- Events -- /** - * @dev Emitted when an oracle is set + * @notice Emitted when an oracle is set * @param index Index of the oracle * @param oracle Address of the oracle */ event OracleSet(uint256 indexed index, address indexed oracle); /** - * @dev Emitted when the vote time limit is set + * @notice Emitted when the vote time limit is set * @param voteTimeLimit Vote time limit in seconds */ event VoteTimeLimitSet(uint256 voteTimeLimit); /** - * @dev Emitted when an oracle votes to deny or allow a subgraph + * @notice Emitted when an oracle votes to deny or allow a subgraph * @param subgraphDeploymentID Subgraph deployment ID * @param deny True to deny, false to allow * @param oracleIndex Index of the oracle voting @@ -72,6 +78,10 @@ contract SubgraphAvailabilityManager is Governed { // -- Modifiers -- + /** + * @dev Modifier to restrict access to authorized oracles only + * @param _oracleIndex Index of the oracle in the oracles array + */ modifier onlyOracle(uint256 _oracleIndex) { require(_oracleIndex < NUM_ORACLES, "SAM: index out of bounds"); require(msg.sender == oracles[_oracleIndex], "SAM: caller must be oracle"); @@ -81,7 +91,7 @@ contract SubgraphAvailabilityManager is Governed { // -- Constructor -- /** - * @dev Contract constructor + * @notice Contract constructor * @param _governor Account that can set or remove oracles and set the vote time limit * @param _rewardsManager Address of the RewardsManager contract * @param _executionThreshold Number of votes required to execute a deny or allow call to the RewardsManager @@ -118,7 +128,7 @@ contract SubgraphAvailabilityManager is Governed { // -- Functions -- /** - * @dev Set the vote time limit. Refreshes all existing votes by incrementing the current nonce. + * @notice Set the vote time limit. Refreshes all existing votes by incrementing the current nonce. * @param _voteTimeLimit Vote time limit in seconds */ function setVoteTimeLimit(uint256 _voteTimeLimit) external onlyGovernor { @@ -128,7 +138,7 @@ contract SubgraphAvailabilityManager is Governed { } /** - * @dev Set oracle address with index. Refreshes all existing votes by incrementing the current nonce. + * @notice Set oracle address with index. Refreshes all existing votes by incrementing the current nonce. * @param _index Index of the oracle * @param _oracle Address of the oracle */ @@ -144,7 +154,7 @@ contract SubgraphAvailabilityManager is Governed { } /** - * @dev Vote deny or allow for a subgraph. + * @notice Vote deny or allow for a subgraph. * NOTE: Can only be called by an oracle. * @param _subgraphDeploymentID Subgraph deployment ID * @param _deny True to deny, false to allow @@ -155,7 +165,7 @@ contract SubgraphAvailabilityManager is Governed { } /** - * @dev Vote deny or allow for many subgraphs. + * @notice Vote deny or allow for many subgraphs. * NOTE: Can only be called by an oracle. * @param _subgraphDeploymentID Array of subgraph deployment IDs * @param _deny Array of booleans, true to deny, false to allow @@ -173,7 +183,7 @@ contract SubgraphAvailabilityManager is Governed { } /** - * @dev Vote deny or allow for a subgraph. + * @notice Vote deny or allow for a subgraph. * When oracles cast their votes we store the timestamp of the vote. * Check if the execution threshold has been reached for a subgraph. * If execution threshold is reached we call the RewardsManager to set the correct state. @@ -203,7 +213,7 @@ contract SubgraphAvailabilityManager is Governed { } /** - * @dev Check if the execution threshold has been reached for a subgraph. + * @notice Check if the execution threshold has been reached for a subgraph. * For a vote to be valid it needs to be within the vote time limit. * @param _subgraphDeploymentID Subgraph deployment ID * @param _deny True to deny, false to allow diff --git a/packages/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol b/packages/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol deleted file mode 100644 index b935682b9..000000000 --- a/packages/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0; -pragma abicoder v2; - -/** - * @title Interface for the L1GraphTokenLockTransferTool contract - * @dev This interface defines the function to get the L2 wallet address for a given L1 token lock wallet. - * The Transfer Tool contract is implemented in the token-distribution repo: https://github.com/graphprotocol/token-distribution/pull/64 - * and is only included here to provide support in L1Staking for the transfer of stake and delegation - * owned by token lock contracts. See GIP-0046 for details: https://forum.thegraph.com/t/4023 - */ -interface IL1GraphTokenLockTransferTool { - /** - * @notice Pulls ETH from an L1 wallet's account to use for L2 ticket gas. - * @dev This function is only callable by the staking contract. - * @param _l1Wallet Address of the L1 token lock wallet - * @param _amount Amount of ETH to pull from the transfer tool contract - */ - function pullETH(address _l1Wallet, uint256 _amount) external; - - /** - * @notice Get the L2 token lock wallet address for a given L1 token lock wallet - * @dev In the actual L1GraphTokenLockTransferTool contract, this is simply the default getter for a public mapping variable. - * @param _l1Wallet Address of the L1 token lock wallet - * @return Address of the L2 token lock wallet if the wallet has an L2 counterpart, or address zero if - * the wallet doesn't have an L2 counterpart (or is not known to be a token lock wallet). - */ - function l2WalletAddress(address _l1Wallet) external view returns (address); -} diff --git a/packages/contracts/contracts/staking/IL1Staking.sol b/packages/contracts/contracts/staking/IL1Staking.sol deleted file mode 100644 index 4a446f787..000000000 --- a/packages/contracts/contracts/staking/IL1Staking.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0; -pragma abicoder v2; - -import { IStaking } from "./IStaking.sol"; -import { IL1StakingBase } from "./IL1StakingBase.sol"; - -/** - * @title Interface for the L1 Staking contract - * @notice This is the interface that should be used when interacting with the L1 Staking contract. - * It extends the IStaking interface with the functions that are specific to L1, adding the transfer tools - * to send stake and delegation to L2. - * @dev Note that L1Staking doesn't actually inherit this interface. This is because of - * the custom setup of the Staking contract where part of the functionality is implemented - * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. - */ -interface IL1Staking is IStaking, IL1StakingBase {} diff --git a/packages/contracts/contracts/staking/IL1StakingBase.sol b/packages/contracts/contracts/staking/IL1StakingBase.sol deleted file mode 100644 index fad2136c2..000000000 --- a/packages/contracts/contracts/staking/IL1StakingBase.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0; -pragma abicoder v2; - -import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; - -/** - * @title Base interface for the L1Staking contract. - * @notice This interface is used to define the transfer tools that are implemented in L1Staking. - * @dev Note it includes only the L1-specific functionality, not the full IStaking interface. - */ -interface IL1StakingBase { - /// @dev Emitted when an indexer transfers their stake to L2. - /// This can happen several times as indexers can transfer partial stake. - event IndexerStakeTransferredToL2( - address indexed indexer, - address indexed l2Indexer, - uint256 transferredStakeTokens - ); - - /// @dev Emitted when a delegator transfers their delegation to L2 - event DelegationTransferredToL2( - address indexed delegator, - address indexed l2Delegator, - address indexed indexer, - address l2Indexer, - uint256 transferredDelegationTokens - ); - - /// @dev Emitted when the L1GraphTokenLockTransferTool is set - event L1GraphTokenLockTransferToolSet(address l1GraphTokenLockTransferTool); - - /// @dev Emitted when a delegator unlocks their tokens ahead of time because the indexer has transferred to L2 - event StakeDelegatedUnlockedDueToL2Transfer(address indexed indexer, address indexed delegator); - - /** - * @notice Set the L1GraphTokenLockTransferTool contract address - * @dev This function can only be called by the governor. - * @param _l1GraphTokenLockTransferTool Address of the L1GraphTokenLockTransferTool contract - */ - function setL1GraphTokenLockTransferTool(IL1GraphTokenLockTransferTool _l1GraphTokenLockTransferTool) external; - - /** - * @notice Send an indexer's stake to L2. - * @dev This function can only be called by the indexer (not an operator). - * It will validate that the remaining stake is sufficient to cover all the allocated - * stake, so the indexer might have to close some allocations before transferring. - * It will also check that the indexer's stake is not locked for withdrawal. - * Since the indexer address might be an L1-only contract, the function takes a beneficiary - * address that will be the indexer's address in L2. - * The caller must provide an amount of ETH to use for the L2 retryable ticket, that - * must be at least `_maxSubmissionCost + _gasPriceBid * _maxGas`. - * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. - * @param _amount Amount of stake GRT to transfer to L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket - */ - function transferStakeToL2( - address _l2Beneficiary, - uint256 _amount, - uint256 _maxGas, - uint256 _gasPriceBid, - uint256 _maxSubmissionCost - ) external payable; - - /** - * @notice Send an indexer's stake to L2, from a GraphTokenLockWallet vesting contract. - * @dev This function can only be called by the indexer (not an operator). - * It will validate that the remaining stake is sufficient to cover all the allocated - * stake, so the indexer might have to close some allocations before transferring. - * It will also check that the indexer's stake is not locked for withdrawal. - * The L2 beneficiary for the stake will be determined by calling the L1GraphTokenLockTransferTool contract, - * so the caller must have previously transferred tokens through that first - * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). - * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of - * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` - * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). - * @param _amount Amount of stake GRT to transfer to L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket - */ - function transferLockedStakeToL2( - uint256 _amount, - uint256 _maxGas, - uint256 _gasPriceBid, - uint256 _maxSubmissionCost - ) external; - - /** - * @notice Send a delegator's delegated tokens to L2 - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the delegation is not locked for undelegation. - * Since the delegator's address might be an L1-only contract, the function takes a beneficiary - * address that will be the delegator's address in L2. - * The caller must provide an amount of ETH to use for the L2 retryable ticket, that - * must be at least `_maxSubmissionCost + _gasPriceBid * _maxGas`. - * @param _indexer Address of the indexer (in L1, before transferring to L2) - * @param _l2Beneficiary Address of the delegator in L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket - */ - function transferDelegationToL2( - address _indexer, - address _l2Beneficiary, - uint256 _maxGas, - uint256 _gasPriceBid, - uint256 _maxSubmissionCost - ) external payable; - - /** - * @notice Send a delegator's delegated tokens to L2, for a GraphTokenLockWallet vesting contract - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the delegation is not locked for undelegation. - * The L2 beneficiary for the delegation will be determined by calling the L1GraphTokenLockTransferTool contract, - * so the caller must have previously transferred tokens through that first - * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). - * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of - * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` - * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). - * @param _indexer Address of the indexer (in L1, before transferring to L2) - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket - */ - function transferLockedDelegationToL2( - address _indexer, - uint256 _maxGas, - uint256 _gasPriceBid, - uint256 _maxSubmissionCost - ) external; - - /** - * @notice Unlock a delegator's delegated tokens, if the indexer has transferred to L2 - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the indexer has no remaining stake in L1. - * The tokens must previously be locked for undelegation by calling `undelegate()`, - * and can be withdrawn with `withdrawDelegated()` immediately after calling this. - * @param _indexer Address of the indexer (in L1, before transferring to L2) - */ - function unlockDelegationToTransferredIndexer(address _indexer) external; -} diff --git a/packages/contracts/contracts/staking/IStaking.sol b/packages/contracts/contracts/staking/IStaking.sol deleted file mode 100644 index a7d89feea..000000000 --- a/packages/contracts/contracts/staking/IStaking.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; -pragma abicoder v2; - -import { IStakingBase } from "./IStakingBase.sol"; -import { IStakingExtension } from "./IStakingExtension.sol"; -import { IMulticall } from "../base/IMulticall.sol"; -import { IManaged } from "../governance/IManaged.sol"; - -/** - * @title Interface for the Staking contract - * @notice This is the interface that should be used when interacting with the Staking contract. - * @dev Note that Staking doesn't actually inherit this interface. This is because of - * the custom setup of the Staking contract where part of the functionality is implemented - * in a separate contract (StakingExtension) to which calls are delegated through the fallback function. - */ -interface IStaking is IStakingBase, IStakingExtension, IMulticall, IManaged {} diff --git a/packages/contracts/contracts/staking/IStakingBase.sol b/packages/contracts/contracts/staking/IStakingBase.sol deleted file mode 100644 index 588144b2a..000000000 --- a/packages/contracts/contracts/staking/IStakingBase.sol +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; -pragma abicoder v2; - -import { IStakingData } from "./IStakingData.sol"; - -/** - * @title Base interface for the Staking contract. - * @dev This interface includes only what's implemented in the base Staking contract. - * It does not include the L1 and L2 specific functionality. It also does not include - * several functions that are implemented in the StakingExtension contract, and are called - * via delegatecall through the fallback function. See IStaking.sol for an interface - * that includes the full functionality. - */ -interface IStakingBase is IStakingData { - /** - * @dev Emitted when `indexer` stakes `tokens` amount. - */ - event StakeDeposited(address indexed indexer, uint256 tokens); - - /** - * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `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` 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` close an allocation in `epoch` for `allocationID`. - * An amount of `tokens` get unallocated from `subgraphDeploymentID`. - * This event also emits the POI (proof of indexing) submitted by the indexer. - * `isPublic` is true if the sender was someone other than the indexer. - */ - event AllocationClosed( - address indexed indexer, - bytes32 indexed subgraphDeploymentID, - uint256 epoch, - uint256 tokens, - address indexed allocationID, - address sender, - bytes32 poi, - bool isPublic - ); - - /** - * @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. - * `epoch` is the protocol epoch the rebate was collected on - * The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees` - * is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt. - * `queryRebates` is the amount distributed to the `indexer` with `delegationFees` collected - * and sent to the delegation pool. - */ - event RebateCollected( - address assetHolder, - address indexed indexer, - bytes32 indexed subgraphDeploymentID, - address indexed allocationID, - uint256 epoch, - uint256 tokens, - uint256 protocolTax, - uint256 curationFees, - uint256 queryFees, - uint256 queryRebates, - uint256 delegationRewards - ); - - /** - * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. - */ - event DelegationParametersUpdated( - address indexed indexer, - uint32 indexingRewardCut, - uint32 queryFeeCut, - uint32 __DEPRECATED_cooldownBlocks // solhint-disable-line var-name-mixedcase - ); - - /** - * @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 Emitted when `extensionImpl` was set as the address of the StakingExtension contract - * to which extended functionality is delegated. - */ - event ExtensionImplementationSet(address indexed extensionImpl); - - /** - * @dev Possible states an allocation can be. - * States: - * - Null = indexer == address(0) - * - Active = not Null && tokens > 0 - * - Closed = Active && closedAtEpoch != 0 - */ - enum AllocationState { - Null, - Active, - Closed - } - - /** - * @notice Initialize this contract. - * @param _controller Address of the controller that manages this contract - * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake - * @param _thawingPeriod Number of blocks that tokens get locked after unstaking - * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) - * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) - * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active - * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating - * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use - * @param _rebatesParameters Alpha and lambda parameters for rebates function - * @param _extensionImpl Address of the StakingExtension implementation - */ - function initialize( - address _controller, - uint256 _minimumIndexerStake, - uint32 _thawingPeriod, - uint32 _protocolPercentage, - uint32 _curationPercentage, - uint32 _maxAllocationEpochs, - uint32 _delegationUnbondingPeriod, - uint32 _delegationRatio, - RebatesParameters calldata _rebatesParameters, - address _extensionImpl - ) external; - - /** - * @notice Set the address of the StakingExtension implementation. - * @dev This function can only be called by the governor. - * @param _extensionImpl Address of the StakingExtension implementation - */ - function setExtensionImpl(address _extensionImpl) external; - - /** - * @notice Set the address of the counterpart (L1 or L2) staking contract. - * @dev This function can only be called by the governor. - * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. - */ - function setCounterpartStakingAddress(address _counterpart) external; - - /** - * @notice Set the minimum stake needed to be an Indexer - * @dev This function can only be called by the governor. - * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake - */ - function setMinimumIndexerStake(uint256 _minimumIndexerStake) external; - - /** - * @notice Set the number of blocks that tokens get locked after unstaking - * @dev This function can only be called by the governor. - * @param _thawingPeriod Number of blocks that tokens get locked after unstaking - */ - function setThawingPeriod(uint32 _thawingPeriod) external; - - /** - * @notice Set the curation percentage of query fees sent to curators. - * @dev This function can only be called by the governor. - * @param _percentage Percentage of query fees sent to curators - */ - function setCurationPercentage(uint32 _percentage) external; - - /** - * @notice Set a protocol percentage to burn when collecting query fees. - * @dev This function can only be called by the governor. - * @param _percentage Percentage of query fees to burn as protocol fee - */ - function setProtocolPercentage(uint32 _percentage) external; - - /** - * @notice Set the max time allowed for indexers to allocate on a subgraph - * before others are allowed to close the allocation. - * @dev This function can only be called by the governor. - * @param _maxAllocationEpochs Allocation duration limit in epochs - */ - function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external; - - /** - * @notice Set the rebate parameters - * @dev This function can only be called by the governor. - * @param _alphaNumerator Numerator of `alpha` - * @param _alphaDenominator Denominator of `alpha` - * @param _lambdaNumerator Numerator of `lambda` - * @param _lambdaDenominator Denominator of `lambda` - */ - function setRebateParameters( - uint32 _alphaNumerator, - uint32 _alphaDenominator, - uint32 _lambdaNumerator, - uint32 _lambdaDenominator - ) external; - - /** - * @notice Authorize or unauthorize an address to be an operator for the caller. - * @param _operator Address to authorize or unauthorize - * @param _allowed Whether the operator is authorized or not - */ - function setOperator(address _operator, bool _allowed) external; - - /** - * @notice Deposit tokens on the indexer's stake. - * The amount staked must be over the minimumIndexerStake. - * @param _tokens Amount of tokens to stake - */ - function stake(uint256 _tokens) external; - - /** - * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. - * The amount staked must be over the minimumIndexerStake. - * @param _indexer Address of the indexer - * @param _tokens Amount of tokens to stake - */ - function stakeTo(address _indexer, uint256 _tokens) external; - - /** - * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. - * @dev NOTE: The function accepts an amount greater than the currently staked tokens. - * If that happens, it will try to unstake the max amount of tokens it can. - * The reason for this behaviour is to avoid time conditions while the transaction - * is in flight. - * @param _tokens Amount of tokens to unstake - */ - function unstake(uint256 _tokens) external; - - /** - * @notice Withdraw indexer tokens once the thawing period has passed. - */ - function withdraw() external; - - /** - * @notice Set the destination where to send rewards for an indexer. - * @param _destination Rewards destination address. If set to zero, rewards will be restaked - */ - function setRewardsDestination(address _destination) external; - - /** - * @notice Set the delegation parameters for the caller. - * @param _indexingRewardCut Percentage of indexing rewards left for the indexer - * @param _queryFeeCut Percentage of query fees left for the indexer - */ - function setDelegationParameters( - uint32 _indexingRewardCut, - uint32 _queryFeeCut, - uint32 // _cooldownBlocks, deprecated - ) external; - - /** - * @notice 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; - - /** - * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. - * The caller must be the indexer or the indexer's operator. - * @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; - - /** - * @notice Close an allocation and free the staked tokens. - * To be eligible for rewards a proof of indexing must be presented. - * Presenting a bad proof is subject to slashable condition. - * To opt out of rewards set _poi to 0x0 - * @param _allocationID The allocation identifier - * @param _poi Proof of indexing submitted for the allocated period - */ - function closeAllocation(address _allocationID, bytes32 _poi) external; - - /** - * @notice Collect query fees from state channels and assign them to an allocation. - * Funds received are only accepted from a valid sender. - * @dev To avoid reverting on the withdrawal from channel flow this function will: - * 1) Accept calls with zero tokens. - * 2) Accept calls after an allocation passed the dispute period, in that case, all - * the received tokens are burned. - * @param _tokens Amount of tokens to collect - * @param _allocationID Allocation where the tokens will be assigned - */ - function collect(uint256 _tokens, address _allocationID) external; - - /** - * @notice Return true if operator is allowed for indexer. - * @param _operator Address of the operator - * @param _indexer Address of the indexer - * @return True if operator is allowed for indexer, false otherwise - */ - function isOperator(address _operator, address _indexer) external view returns (bool); - - /** - * @notice Getter that returns if an indexer has any stake. - * @param _indexer Address of the indexer - * @return True if indexer has staked tokens - */ - function hasStake(address _indexer) external view returns (bool); - - /** - * @notice Get the total amount of tokens staked by the indexer. - * @param _indexer Address of the indexer - * @return Amount of tokens staked by the indexer - */ - function getIndexerStakedTokens(address _indexer) external view returns (uint256); - - /** - * @notice 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 available to allocate including delegation - */ - function getIndexerCapacity(address _indexer) external view returns (uint256); - - /** - * @notice Return the allocation by ID. - * @param _allocationID Address used as allocation identifier - * @return Allocation data - */ - function getAllocation(address _allocationID) external view returns (Allocation memory); - - /** - * @dev New function to get the allocation data for the rewards manager - * @dev Note that this is only to make tests pass, as the staking contract with - * this changes will never get deployed. HorizonStaking is taking it's place. - */ - function getAllocationData( - address _allocationID - ) external view returns (bool, address, bytes32, uint256, uint256, uint256); - - /** - * @dev New function to get the allocation active status for the rewards manager - * @dev Note that this is only to make tests pass, as the staking contract with - * this changes will never get deployed. HorizonStaking is taking it's place. - */ - function isActiveAllocation(address _allocationID) external view returns (bool); - - /** - * @notice Return the current state of an allocation - * @param _allocationID Allocation identifier - * @return AllocationState enum with the state of the allocation - */ - function getAllocationState(address _allocationID) external view returns (AllocationState); - - /** - * @notice Return if allocationID is used. - * @param _allocationID Address used as signer by the indexer for an allocation - * @return True if allocationID already used - */ - function isAllocation(address _allocationID) external view returns (bool); - - /** - * @notice Return the total amount of tokens allocated to subgraph. - * @param _subgraphDeploymentID Deployment ID for the subgraph - * @return Total tokens allocated to subgraph - */ - function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) external view returns (uint256); -} diff --git a/packages/contracts/contracts/staking/IStakingData.sol b/packages/contracts/contracts/staking/IStakingData.sol deleted file mode 100644 index 149e3b8a6..000000000 --- a/packages/contracts/contracts/staking/IStakingData.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; - -/** - * @title Staking Data interface - * @dev This interface defines some structures used by the Staking contract. - */ -interface IStakingData { - /** - * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment - * An allocation is created in the allocate() function and closed in closeAllocation() - */ - struct Allocation { - address indexer; - bytes32 subgraphDeploymentID; - uint256 tokens; // Tokens allocated to a SubgraphDeployment - uint256 createdAtEpoch; // Epoch when it was created - uint256 closedAtEpoch; // Epoch when it was closed - uint256 collectedFees; // Collected fees for the allocation - uint256 __DEPRECATED_effectiveAllocation; // solhint-disable-line var-name-mixedcase - uint256 accRewardsPerAllocatedToken; // Snapshot used for reward calc - uint256 distributedRebates; // Collected rebates that have been rebated - } - - // -- Delegation Data -- - - /** - * @dev Delegation pool information. One per indexer. - */ - struct DelegationPool { - uint32 __DEPRECATED_cooldownBlocks; // solhint-disable-line var-name-mixedcase - uint32 indexingRewardCut; // in PPM - uint32 queryFeeCut; // in PPM - uint256 updatedAtBlock; // Block when the pool was last updated - uint256 tokens; // Total tokens as pool reserves - uint256 shares; // Total shares minted in the pool - mapping(address => Delegation) delegators; // Mapping of delegator => Delegation - } - - /** - * @dev Individual delegation data of a delegator in a pool. - */ - struct Delegation { - uint256 shares; // Shares owned by a delegator in the pool - uint256 tokensLocked; // Tokens locked for undelegation - uint256 tokensLockedUntil; // Epoch when locked tokens can be withdrawn - } - - /** - * @dev Rebates parameters. Used to avoid stack too deep errors in Staking initialize function. - */ - struct RebatesParameters { - uint32 alphaNumerator; - uint32 alphaDenominator; - uint32 lambdaNumerator; - uint32 lambdaDenominator; - } -} diff --git a/packages/contracts/contracts/staking/IStakingExtension.sol b/packages/contracts/contracts/staking/IStakingExtension.sol deleted file mode 100644 index 9a998aac5..000000000 --- a/packages/contracts/contracts/staking/IStakingExtension.sol +++ /dev/null @@ -1,292 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; -pragma abicoder v2; - -import { IStakingData } from "./IStakingData.sol"; -import { IStakes } from "./libs/IStakes.sol"; - -/** - * @title Interface for the StakingExtension contract - * @dev This interface defines the events and functions implemented - * in the StakingExtension contract, which is used to extend the functionality - * of the Staking contract while keeping it within the 24kB mainnet size limit. - * In particular, this interface includes delegation functions and various storage - * getters. - */ -interface IStakingExtension is IStakingData { - /** - * @dev DelegationPool struct as returned by delegationPools(), since - * the original DelegationPool in IStakingData.sol contains a nested mapping. - */ - struct DelegationPoolReturn { - uint32 __DEPRECATED_cooldownBlocks; // solhint-disable-line var-name-mixedcase - uint32 indexingRewardCut; // in PPM - uint32 queryFeeCut; // in PPM - uint256 updatedAtBlock; // Block when the pool was last updated - uint256 tokens; // Total tokens as pool reserves - uint256 shares; // Total shares minted in the pool - } - - /** - * @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` 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 `caller` set `slasher` address as `allowed` to slash stakes. - */ - event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); - - /** - * @notice 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 - * @dev This function is only callable by the governor - * @param _delegationRatio Delegation capacity multiplier - */ - function setDelegationRatio(uint32 _delegationRatio) external; - - /** - * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. - * @dev This function is only callable by the governor - * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating - */ - function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external; - - /** - * @notice Set a delegation tax percentage to burn when delegated funds are deposited. - * @dev This function is only callable by the governor - * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million - */ - function setDelegationTaxPercentage(uint32 _percentage) external; - - /** - * @notice Set or unset an address as allowed slasher. - * @dev This function can only be called by the governor. - * @param _slasher Address of the party allowed to slash indexers - * @param _allowed True if slasher is allowed - */ - function setSlasher(address _slasher, bool _allowed) external; - - /** - * @notice Delegate tokens to an indexer. - * @param _indexer Address of the indexer to which tokens are delegated - * @param _tokens Amount of tokens to delegate - * @return Amount of shares issued from the delegation pool - */ - function delegate(address _indexer, uint256 _tokens) external returns (uint256); - - /** - * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. - * @param _indexer Address of the indexer to which 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 returns (uint256); - - /** - * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally - * re-delegate to a new indexer. - * @param _indexer Withdraw available tokens delegated to indexer - * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address - */ - function withdrawDelegated(address _indexer, address _newIndexer) external returns (uint256); - - /** - * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. - * @dev Can only be called by the slasher role. - * @param _indexer Address of indexer to slash - * @param _tokens Amount of tokens to slash from the indexer stake - * @param _reward Amount of reward tokens to send to a beneficiary - * @param _beneficiary Address of a beneficiary to receive a reward for the slashing - */ - function slash(address _indexer, uint256 _tokens, uint256 _reward, address _beneficiary) external; - - /** - * @notice 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 returns (Delegation memory); - - /** - * @notice 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 has tokens delegated to the indexer - */ - function isDelegator(address _indexer, address _delegator) external view returns (bool); - - /** - * @notice 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) external view returns (uint256); - - /** - * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: - * If delegation ratio is 100, and an Indexer has staked 5 GRT, - * then they can use up to 500 GRT from the delegated stake - * @return Delegation ratio - */ - function delegationRatio() external view returns (uint32); - - /** - * @notice Getter for delegationUnbondingPeriod: - * Time in epochs a delegator needs to wait to withdraw delegated stake - * @return Delegation unbonding period in epochs - */ - function delegationUnbondingPeriod() external view returns (uint32); - - /** - * @notice Getter for delegationTaxPercentage: - * Percentage of tokens to tax a delegation deposit, expressed in parts per million - * @return Delegation tax percentage in parts per million - */ - function delegationTaxPercentage() external view returns (uint32); - - /** - * @notice Getter for delegationPools[_indexer]: - * gets the delegation pool structure for a particular indexer. - * @param _indexer Address of the indexer for which to query the delegation pool - * @return Delegation pool as a DelegationPoolReturn struct - */ - function delegationPools(address _indexer) external view returns (DelegationPoolReturn memory); - - /** - * @notice Getter for operatorAuth[_indexer][_maybeOperator]: - * returns true if the operator is authorized to operate on behalf of the indexer. - * @param _indexer The indexer address for which to query authorization - * @param _maybeOperator The address that may or may not be an operator - * @return True if the operator is authorized to operate on behalf of the indexer - */ - function operatorAuth(address _indexer, address _maybeOperator) external view returns (bool); - - /** - * @notice Getter for rewardsDestination[_indexer]: - * returns the address where the indexer's rewards are sent. - * @param _indexer The indexer address for which to query the rewards destination - * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked - */ - function rewardsDestination(address _indexer) external view returns (address); - - /** - * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: - * returns the amount of tokens allocated to a subgraph deployment. - * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations - * @return The amount of tokens allocated to the subgraph deployment - */ - function subgraphAllocations(bytes32 _subgraphDeploymentId) external view returns (uint256); - - /** - * @notice Getter for slashers[_maybeSlasher]: - * returns true if the address is a slasher, i.e. an entity that can slash indexers - * @param _maybeSlasher Address for which to check the slasher role - * @return True if the address is a slasher - */ - function slashers(address _maybeSlasher) external view returns (bool); - - /** - * @notice Getter for minimumIndexerStake: the minimum - * amount of GRT that an indexer needs to stake. - * @return Minimum indexer stake in GRT - */ - function minimumIndexerStake() external view returns (uint256); - - /** - * @notice Getter for thawingPeriod: the time in blocks an - * indexer needs to wait to unstake tokens. - * @return Thawing period in blocks - */ - function thawingPeriod() external view returns (uint32); - - /** - * @notice Getter for curationPercentage: the percentage of - * query fees that are distributed to curators. - * @return Curation percentage in parts per million - */ - function curationPercentage() external view returns (uint32); - - /** - * @notice Getter for protocolPercentage: the percentage of - * query fees that are burned as protocol fees. - * @return Protocol percentage in parts per million - */ - function protocolPercentage() external view returns (uint32); - - /** - * @notice Getter for maxAllocationEpochs: the maximum time in epochs - * that an allocation can be open before anyone is allowed to close it. This - * also caps the effective allocation when sending the allocation's query fees - * to the rebate pool. - * @return Maximum allocation period in epochs - */ - function maxAllocationEpochs() external view returns (uint32); - - /** - * @notice Getter for the numerator of the rebates alpha parameter - * @return Alpha numerator - */ - function alphaNumerator() external view returns (uint32); - - /** - * @notice Getter for the denominator of the rebates alpha parameter - * @return Alpha denominator - */ - function alphaDenominator() external view returns (uint32); - - /** - * @notice Getter for the numerator of the rebates lambda parameter - * @return Lambda numerator - */ - function lambdaNumerator() external view returns (uint32); - - /** - * @notice Getter for the denominator of the rebates lambda parameter - * @return Lambda denominator - */ - function lambdaDenominator() external view returns (uint32); - - /** - * @notice Getter for stakes[_indexer]: - * gets the stake information for an indexer as a IStakes.Indexer struct. - * @param _indexer Indexer address for which to query the stake information - * @return Stake information for the specified indexer, as a IStakes.Indexer struct - */ - function stakes(address _indexer) external view returns (IStakes.Indexer memory); - - /** - * @notice Getter for allocations[_allocationID]: - * gets an allocation's information as an IStakingData.Allocation struct. - * @param _allocationID Allocation ID for which to query the allocation information - * @return The specified allocation, as an IStakingData.Allocation struct - */ - function allocations(address _allocationID) external view returns (IStakingData.Allocation memory); -} diff --git a/packages/contracts/contracts/staking/L1Staking.sol b/packages/contracts/contracts/staking/L1Staking.sol index 78ce8bc63..b2a00c57e 100644 --- a/packages/contracts/contracts/staking/L1Staking.sol +++ b/packages/contracts/contracts/staking/L1Staking.sol @@ -3,23 +3,27 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { ITokenGateway } from "../arbitrum/ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { Staking } from "./Staking.sol"; import { Stakes } from "./libs/Stakes.sol"; -import { IStakes } from "./libs/IStakes.sol"; -import { IStakingData } from "./IStakingData.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; +import { IStakingData } from "@graphprotocol/interfaces/contracts/contracts/staking/IStakingData.sol"; import { L1StakingV1Storage } from "./L1StakingStorage.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; -import { IL1StakingBase } from "./IL1StakingBase.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; +import { IL1StakingBase } from "@graphprotocol/interfaces/contracts/contracts/staking/IL1StakingBase.sol"; import { MathUtils } from "./libs/MathUtils.sol"; -import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; -import { IL2StakingTypes } from "../l2/staking/IL2StakingTypes.sol"; +import { IL1GraphTokenLockTransferTool } from "@graphprotocol/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol"; +import { IL2StakingTypes } from "@graphprotocol/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol"; /** * @title L1Staking contract - * @dev This contract is the L1 variant of the Staking contract. It adds functions + * @author Edge & Node + * @notice This contract is the L1 variant of the Staking contract. It adds functions * to send an indexer's stake to L2, and to send delegation to L2 as well. */ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { @@ -36,9 +40,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Set the L1GraphTokenLockTransferTool contract address - * @dev This function can only be called by the governor. - * @param _l1GraphTokenLockTransferTool Address of the L1GraphTokenLockTransferTool contract + * @inheritdoc IL1StakingBase */ function setL1GraphTokenLockTransferTool( IL1GraphTokenLockTransferTool _l1GraphTokenLockTransferTool @@ -48,21 +50,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Send an indexer's stake to L2. - * @dev This function can only be called by the indexer (not an operator). - * It will validate that the remaining stake is sufficient to cover all the allocated - * stake, so the indexer might have to close some allocations before transferring. - * It will also check that the indexer's stake is not locked for withdrawal. - * Since the indexer address might be an L1-only contract, the function takes a beneficiary - * address that will be the indexer's address in L2. - * The caller must provide an amount of ETH to use for the L2 retryable ticket, that - * must be at _exactly_ `_maxSubmissionCost + _gasPriceBid * _maxGas`. - * Any refunds for the submission fee or L2 gas will be lost. - * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. - * @param _amount Amount of stake GRT to transfer to L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @inheritdoc IL1StakingBase */ function transferStakeToL2( address _l2Beneficiary, @@ -76,22 +64,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Send an indexer's stake to L2, from a GraphTokenLockWallet vesting contract. - * @dev This function can only be called by the indexer (not an operator). - * It will validate that the remaining stake is sufficient to cover all the allocated - * stake, so the indexer might have to close some allocations before transferring. - * It will also check that the indexer's stake is not locked for withdrawal. - * The L2 beneficiary for the stake will be determined by calling the L1GraphTokenLockTransferTool contract, - * so the caller must have previously transferred tokens through that first - * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). - * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of - * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` - * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). - * Any refunds for the submission fee or L2 gas will be lost. - * @param _amount Amount of stake GRT to transfer to L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @inheritdoc IL1StakingBase */ function transferLockedStakeToL2( uint256 _amount, @@ -109,20 +82,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Send a delegator's delegated tokens to L2 - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the delegation is not locked for undelegation. - * Since the delegator's address might be an L1-only contract, the function takes a beneficiary - * address that will be the delegator's address in L2. - * The caller must provide an amount of ETH to use for the L2 retryable ticket, that - * must be _exactly_ `_maxSubmissionCost + _gasPriceBid * _maxGas`. - * Any refunds for the submission fee or L2 gas will be lost. - * @param _indexer Address of the indexer (in L1, before transferring to L2) - * @param _l2Beneficiary Address of the delegator in L2 - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @inheritdoc IL1StakingBase */ function transferDelegationToL2( address _indexer, @@ -144,21 +104,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Send a delegator's delegated tokens to L2, for a GraphTokenLockWallet vesting contract - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the delegation is not locked for undelegation. - * The L2 beneficiary for the delegation will be determined by calling the L1GraphTokenLockTransferTool contract, - * so the caller must have previously transferred tokens through that first - * (see GIP-0046 for details: https://forum.thegraph.com/t/4023). - * The ETH for the L2 gas will be pulled from the L1GraphTokenLockTransferTool, so the owner of - * the GraphTokenLockWallet must have previously deposited at least `_maxSubmissionCost + _gasPriceBid * _maxGas` - * ETH into the L1GraphTokenLockTransferTool contract (using its depositETH function). - * Any refunds for the submission fee or L2 gas will be lost. - * @param _indexer Address of the indexer (in L1, before transferring to L2) - * @param _maxGas Max gas to use for the L2 retryable ticket - * @param _gasPriceBid Gas price bid for the L2 retryable ticket - * @param _maxSubmissionCost Max submission cost for the L2 retryable ticket + * @inheritdoc IL1StakingBase */ function transferLockedDelegationToL2( address _indexer, @@ -184,13 +130,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @notice Unlock a delegator's delegated tokens, if the indexer has transferred to L2 - * @dev This function can only be called by the delegator. - * This function will validate that the indexer has transferred their stake using transferStakeToL2, - * and that the indexer has no remaining stake in L1. - * The tokens must previously be locked for undelegation by calling `undelegate()`, - * and can be withdrawn with `withdrawDelegated()` immediately after calling this. - * @param _indexer Address of the indexer (in L1, before transferring to L2) + * @inheritdoc IL1StakingBase */ function unlockDelegationToTransferredIndexer(address _indexer) external override notPartialPaused { require( @@ -209,13 +149,14 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @dev Implements sending an indexer's stake to L2. + * @notice Implements sending an indexer's stake to L2. * This function can only be called by the indexer (not an operator). * It will validate that the remaining stake is sufficient to cover all the allocated * stake, so the indexer might have to close some allocations before transferring. * It will also check that the indexer's stake is not locked for withdrawal. * Since the indexer address might be an L1-only contract, the function takes a beneficiary * address that will be the indexer's address in L2. + * @param _indexer Address of the indexer transferring stake * @param _l2Beneficiary Address of the indexer in L2. If the indexer has previously transferred stake, this must match the previously-used value. * @param _amount Amount of stake GRT to transfer to L2 * @param _maxGas Max gas to use for the L2 retryable ticket @@ -282,12 +223,13 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @dev Implements sending a delegator's delegated tokens to L2. + * @notice Implements sending a delegator's delegated tokens to L2. * This function can only be called by the delegator. * This function will validate that the indexer has transferred their stake using transferStakeToL2, * and that the delegation is not locked for undelegation. * Since the delegator's address might be an L1-only contract, the function takes a beneficiary * address that will be the delegator's address in L2. + * @param _delegator Address of the delegator transferring delegation * @param _indexer Address of the indexer (in L1, before transferring to L2) * @param _l2Beneficiary Address of the delegator in L2 * @param _maxGas Max gas to use for the L2 retryable ticket @@ -352,7 +294,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase { } /** - * @dev Sends a message to the L2Staking with some extra data, + * @notice Sends a message to the L2Staking with some extra data, * also sending some tokens, using the L1GraphTokenGateway. * @param _tokens Amount of tokens to send to L2 * @param _maxGas Max gas to use for the L2 retryable ticket diff --git a/packages/contracts/contracts/staking/L1StakingStorage.sol b/packages/contracts/contracts/staking/L1StakingStorage.sol index bd7a7f0ee..5ee76f633 100644 --- a/packages/contracts/contracts/staking/L1StakingStorage.sol +++ b/packages/contracts/contracts/staking/L1StakingStorage.sol @@ -1,18 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable named-parameters-mapping + pragma solidity ^0.7.6; pragma abicoder v2; -import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; +import { IL1GraphTokenLockTransferTool } from "@graphprotocol/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol"; /** * @title L1StakingV1Storage + * @author Edge & Node * @notice This contract holds all the L1-specific storage variables for the L1Staking contract, version 1 * @dev When adding new versions, make sure to move the gap to the new version and * reduce the size of the gap accordingly. */ abstract contract L1StakingV1Storage { - /// If an indexer has transferred to L2, this mapping will hold the indexer's address in L2 + /// @notice If an indexer has transferred to L2, this mapping will hold the indexer's address in L2 mapping(address => address) public indexerTransferredToL2; /// @dev For locked indexers/delegations, this contract holds the mapping of L1 to L2 addresses IL1GraphTokenLockTransferTool internal l1GraphTokenLockTransferTool; diff --git a/packages/contracts/contracts/staking/Staking.sol b/packages/contracts/contracts/staking/Staking.sol index c61fe0ded..4b7551d02 100644 --- a/packages/contracts/contracts/staking/Staking.sol +++ b/packages/contracts/contracts/staking/Staking.sol @@ -3,27 +3,31 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol"; import { Multicall } from "../base/Multicall.sol"; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; import { TokenUtils } from "../utils/TokenUtils.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; -import { IStakingBase } from "./IStakingBase.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; +import { IStakingBase } from "@graphprotocol/interfaces/contracts/contracts/staking/IStakingBase.sol"; import { StakingV4Storage } from "./StakingStorage.sol"; import { MathUtils } from "./libs/MathUtils.sol"; import { Stakes } from "./libs/Stakes.sol"; -import { IStakes } from "./libs/IStakes.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; import { Managed } from "../governance/Managed.sol"; -import { ICuration } from "../curation/ICuration.sol"; -import { IRewardsManager } from "../rewards/IRewardsManager.sol"; +import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; +import { IRewardsManager } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsManager.sol"; import { StakingExtension } from "./StakingExtension.sol"; import { LibExponential } from "./libs/Exponential.sol"; /** * @title Base Staking contract - * @dev The Staking contract allows Indexers to Stake on Subgraphs. Indexers Stake by creating + * @author Edge & Node + * @notice The Staking contract allows Indexers to Stake on Subgraphs. Indexers Stake by creating * Allocations on a Subgraph. It also allows Delegators to Delegate towards an Indexer. The * contract also has the slashing functionality. * The contract is abstract as the implementation that is deployed depends on each layer: L1Staking on mainnet @@ -45,9 +49,11 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M * @dev This function does not return to its internal call site, it will return directly to the * external caller. */ - // solhint-disable-next-line payable-fallback, no-complex-fallback fallback() external { + // solhint-disable-previous-line payable-fallback, no-complex-fallback + require(_implementation() != address(0), "only through proxy"); + // solhint-disable-next-line no-inline-assembly assembly { // (a) get free memory pointer @@ -80,17 +86,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Initialize this contract. - * @param _controller Address of the controller that manages this contract - * @param _minimumIndexerStake Minimum amount of tokens that an indexer must stake - * @param _thawingPeriod Number of epochs that tokens get locked after unstaking - * @param _protocolPercentage Percentage of query fees that are burned as protocol fee (in PPM) - * @param _curationPercentage Percentage of query fees that are given to curators (in PPM) - * @param _maxAllocationEpochs The maximum number of epochs that an allocation can be active - * @param _delegationUnbondingPeriod The period in epochs that tokens get locked after undelegating - * @param _delegationRatio The ratio between an indexer's own stake and the delegation they can use - * @param _rebatesParameters Alpha and lambda parameters for rebates function - * @param _extensionImpl Address of the StakingExtension implementation + * @inheritdoc IStakingBase */ function initialize( address _controller, @@ -140,9 +136,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Set the address of the StakingExtension implementation. - * @dev This function can only be called by the governor. - * @param _extensionImpl Address of the StakingExtension implementation + * @inheritdoc IStakingBase */ function setExtensionImpl(address _extensionImpl) external override onlyGovernor { extensionImpl = _extensionImpl; @@ -150,9 +144,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Set the address of the counterpart (L1 or L2) staking contract. - * @dev This function can only be called by the governor. - * @param _counterpart Address of the counterpart staking contract in the other chain, without any aliasing. + * @inheritdoc IStakingBase */ function setCounterpartStakingAddress(address _counterpart) external override onlyGovernor { counterpartStakingAddress = _counterpart; @@ -160,52 +152,42 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Set the minimum stake required to be an indexer. - * @param _minimumIndexerStake Minimum indexer stake + * @inheritdoc IStakingBase */ function setMinimumIndexerStake(uint256 _minimumIndexerStake) external override onlyGovernor { _setMinimumIndexerStake(_minimumIndexerStake); } /** - * @notice Set the thawing period for unstaking. - * @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking + * @inheritdoc IStakingBase */ function setThawingPeriod(uint32 _thawingPeriod) external override onlyGovernor { _setThawingPeriod(_thawingPeriod); } /** - * @notice Set the curation percentage of query fees sent to curators. - * @param _percentage Percentage of query fees sent to curators + * @inheritdoc IStakingBase */ function setCurationPercentage(uint32 _percentage) external override onlyGovernor { _setCurationPercentage(_percentage); } /** - * @notice Set a protocol percentage to burn when collecting query fees. - * @param _percentage Percentage of query fees to burn as protocol fee + * @inheritdoc IStakingBase */ function setProtocolPercentage(uint32 _percentage) external override onlyGovernor { _setProtocolPercentage(_percentage); } /** - * @notice Set the max time allowed for indexers to allocate on a subgraph - * before others are allowed to close the allocation. - * @param _maxAllocationEpochs Allocation duration limit in epochs + * @inheritdoc IStakingBase */ function setMaxAllocationEpochs(uint32 _maxAllocationEpochs) external override onlyGovernor { _setMaxAllocationEpochs(_maxAllocationEpochs); } /** - * @dev Set the rebate parameters. - * @param _alphaNumerator Numerator of `alpha` in the rebates function - * @param _alphaDenominator Denominator of `alpha` in the rebates function - * @param _lambdaNumerator Numerator of `lambda` in the rebates function - * @param _lambdaDenominator Denominator of `lambda` in the rebates function + * @inheritdoc IStakingBase */ function setRebateParameters( uint32 _alphaNumerator, @@ -217,9 +199,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Authorize or unauthorize an address to be an operator for the caller. - * @param _operator Address to authorize or unauthorize - * @param _allowed Whether the operator is authorized or not + * @inheritdoc IStakingBase */ function setOperator(address _operator, bool _allowed) external override { require(_operator != msg.sender, "operator == sender"); @@ -228,21 +208,18 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Deposit tokens on the indexer's stake. - * The amount staked must be over the minimumIndexerStake. - * @param _tokens Amount of tokens to stake + * @inheritdoc IStakingBase */ function stake(uint256 _tokens) external override { stakeTo(msg.sender, _tokens); } /** - * @notice Unstake tokens from the indexer stake, lock them until the thawing period expires. + * @inheritdoc IStakingBase * @dev NOTE: The function accepts an amount greater than the currently staked tokens. * If that happens, it will try to unstake the max amount of tokens it can. * The reason for this behaviour is to avoid time conditions while the transaction * is in flight. - * @param _tokens Amount of tokens to unstake */ function unstake(uint256 _tokens) external override notPartialPaused { address indexer = msg.sender; @@ -271,15 +248,14 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Withdraw indexer tokens once the thawing period has passed. + * @inheritdoc IStakingBase */ function withdraw() external override notPaused { _withdraw(msg.sender); } /** - * @notice Set the destination where to send rewards for an indexer. - * @param _destination Rewards destination address. If set to zero, rewards will be restaked + * @inheritdoc IStakingBase */ function setRewardsDestination(address _destination) external override { __rewardsDestination[msg.sender] = _destination; @@ -287,12 +263,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice 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)` + * @inheritdoc IStakingBase */ function allocate( bytes32 _subgraphDeploymentID, @@ -305,14 +276,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Allocate available tokens to a subgraph deployment from and indexer's stake. - * The caller must be the indexer or the indexer's operator. - * @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)` + * @inheritdoc IStakingBase */ function allocateFrom( address _indexer, @@ -326,25 +290,20 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Close an allocation and free the staked tokens. - * To be eligible for rewards a proof of indexing must be presented. + * @inheritdoc IStakingBase + * @dev To be eligible for rewards a proof of indexing must be presented. * Presenting a bad proof is subject to slashable condition. * To opt out of rewards set _poi to 0x0 - * @param _allocationID The allocation identifier - * @param _poi Proof of indexing submitted for the allocated period */ function closeAllocation(address _allocationID, bytes32 _poi) external override notPaused { _closeAllocation(_allocationID, _poi); } /** - * @dev Collect and rebate query fees from state channels to the indexer - * To avoid reverting on the withdrawal from channel flow this function will accept calls with zero tokens. - * We use an exponential rebate formula to calculate the amount of tokens to rebate to the indexer. + * @inheritdoc IStakingBase + * @dev We use an exponential rebate formula to calculate the amount of tokens to rebate to the indexer. * This implementation allows collecting multiple times on the same allocation, keeping track of the * total amount rebated, the total amount collected and compensating the indexer for the difference. - * @param _tokens Amount of tokens to collect - * @param _allocationID Allocation where the tokens will be assigned */ function collect(uint256 _tokens, address _allocationID) external override { // Allocation identifier validation @@ -447,36 +406,28 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Return if allocationID is used. - * @param _allocationID Address used as signer by the indexer for an allocation - * @return True if allocationID already used + * @inheritdoc IStakingBase */ function isAllocation(address _allocationID) external view override returns (bool) { return _getAllocationState(_allocationID) != AllocationState.Null; } /** - * @notice Getter that returns if an indexer has any stake. - * @param _indexer Address of the indexer - * @return True if indexer has staked tokens + * @inheritdoc IStakingBase */ function hasStake(address _indexer) external view override returns (bool) { return __stakes[_indexer].tokensStaked > 0; } /** - * @notice Return the allocation by ID. - * @param _allocationID Address used as allocation identifier - * @return Allocation data + * @inheritdoc IStakingBase */ function getAllocation(address _allocationID) external view override returns (Allocation memory) { return __allocations[_allocationID]; } /** - * @dev New function to get the allocation data for the rewards manager - * @dev Note that this is only to make tests pass, as the staking contract with - * this changes will never get deployed. HorizonStaking is taking it's place. + * @inheritdoc IStakingBase */ function getAllocationData( address _allocationID @@ -495,46 +446,35 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev New function to get the allocation active status for the rewards manager - * @dev Note that this is only to make tests pass, as the staking contract with - * this changes will never get deployed. HorizonStaking is taking it's place. + * @inheritdoc IStakingBase */ function isActiveAllocation(address _allocationID) external view override returns (bool) { return _getAllocationState(_allocationID) == AllocationState.Active; } /** - * @notice Return the current state of an allocation - * @param _allocationID Allocation identifier - * @return AllocationState enum with the state of the allocation + * @inheritdoc IStakingBase */ function getAllocationState(address _allocationID) external view override returns (AllocationState) { return _getAllocationState(_allocationID); } /** - * @notice Return the total amount of tokens allocated to subgraph. - * @param _subgraphDeploymentID Deployment ID for the subgraph - * @return Total tokens allocated to subgraph + * @inheritdoc IStakingBase */ function getSubgraphAllocatedTokens(bytes32 _subgraphDeploymentID) external view override returns (uint256) { return __subgraphAllocations[_subgraphDeploymentID]; } /** - * @notice Get the total amount of tokens staked by the indexer. - * @param _indexer Address of the indexer - * @return Amount of tokens staked by the indexer + * @inheritdoc IStakingBase */ function getIndexerStakedTokens(address _indexer) external view override returns (uint256) { return __stakes[_indexer].tokensStaked; } /** - * @notice Deposit tokens on the Indexer stake, on behalf of the Indexer. - * The amount staked must be over the minimumIndexerStake. - * @param _indexer Address of the indexer - * @param _tokens Amount of tokens to stake + * @inheritdoc IStakingBase */ function stakeTo(address _indexer, uint256 _tokens) public override notPartialPaused { require(_tokens > 0, "!tokens"); @@ -547,9 +487,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Set the delegation parameters for the caller. - * @param _indexingRewardCut Percentage of indexing rewards left for the indexer - * @param _queryFeeCut Percentage of query fees left for the indexer + * @inheritdoc IStakingBase */ function setDelegationParameters( uint32 _indexingRewardCut, @@ -560,10 +498,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice 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 available to allocate including delegation + * @inheritdoc IStakingBase */ function getIndexerCapacity(address _indexer) public view override returns (uint256) { IStakes.Indexer memory indexerStake = __stakes[_indexer]; @@ -576,17 +511,14 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @notice Return true if operator is allowed for indexer. - * @param _operator Address of the operator - * @param _indexer Address of the indexer - * @return True if operator is allowed for indexer, false otherwise + * @inheritdoc IStakingBase */ function isOperator(address _operator, address _indexer) public view override returns (bool) { return __operatorAuth[_indexer][_operator]; } /** - * @dev Internal: Set the minimum indexer stake required. + * @notice Internal: Set the minimum indexer stake required. * @param _minimumIndexerStake Minimum indexer stake */ function _setMinimumIndexerStake(uint256 _minimumIndexerStake) private { @@ -596,7 +528,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Internal: Set the thawing period for unstaking. + * @notice Internal: Set the thawing period for unstaking. * @param _thawingPeriod Period in blocks to wait for token withdrawals after unstaking */ function _setThawingPeriod(uint32 _thawingPeriod) private { @@ -606,7 +538,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Internal: Set the curation percentage of query fees sent to curators. + * @notice 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 { @@ -617,7 +549,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Internal: Set a protocol percentage to burn when collecting query fees. + * @notice 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 { @@ -628,7 +560,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Internal: Set the max time allowed for indexers stake on allocations. + * @notice Internal: Set the max time allowed for indexers stake on allocations. * @param _maxAllocationEpochs Allocation duration limit in epochs */ function _setMaxAllocationEpochs(uint32 _maxAllocationEpochs) private { @@ -637,7 +569,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Set the rebate parameters. + * @notice Set the rebate parameters. * @param _alphaNumerator Numerator of `alpha` in the rebates function * @param _alphaDenominator Denominator of `alpha` in the rebates function * @param _lambdaNumerator Numerator of `lambda` in the rebates function @@ -660,7 +592,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Set the delegation parameters for a particular indexer. + * @notice Set the delegation parameters for a particular indexer. * @param _indexer Indexer to set delegation parameters * @param _indexingRewardCut Percentage of indexing rewards left for delegators * @param _queryFeeCut Percentage of query fees left for delegators @@ -681,7 +613,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Stake tokens on the indexer. + * @notice 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 @@ -704,7 +636,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Withdraw indexer tokens once the thawing period has passed. + * @notice Withdraw indexer tokens once the thawing period has passed. * @param _indexer Address of indexer to withdraw funds from */ function _withdraw(address _indexer) private { @@ -719,7 +651,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Allocate available tokens to a subgraph deployment. + * @notice 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 @@ -794,7 +726,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Close an allocation and free the staked tokens. + * @notice Close an allocation and free the staked tokens. * @param _allocationID The allocation identifier * @param _poi Proof of indexing submitted for the allocated period */ @@ -861,7 +793,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Collect the delegation rewards for query fees. + * @notice 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 @@ -879,7 +811,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Collect the delegation rewards for indexing. + * @notice 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 @@ -897,7 +829,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Collect the curation fees for a subgraph deployment from an amount of tokens. + * @notice 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 @@ -926,7 +858,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M if (curationFees > 0) { // Transfer and call collect() // This function transfer tokens to a trusted protocol contracts - // Then we call collect() to do the transfer Bookkeeping + // Then we call collect() to do the transfer bookkeeping rewardsManager().onSubgraphSignalUpdate(_subgraphDeploymentID); TokenUtils.pushTokens(_graphToken, address(curation), curationFees); curation.collect(_subgraphDeploymentID, curationFees); @@ -937,7 +869,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Collect tax to burn for an amount of tokens. + * @notice 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 @@ -953,7 +885,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Triggers an update of rewards due to a change in allocations. + * @notice Triggers an update of rewards due to a change in allocations. * @param _subgraphDeploymentID Subgraph deployment updated * @return Accumulated rewards per allocated token for the subgraph deployment */ @@ -966,7 +898,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Assign rewards for the closed allocation to indexer and delegators. + * @notice Assign rewards for the closed allocation to indexer and delegators. * @param _allocationID Allocation * @param _indexer Address of the indexer that did the allocation */ @@ -993,7 +925,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Send rewards to the appropriate destination. + * @notice Send rewards to the appropriate destination. * @param _graphToken Graph token * @param _amount Number of rewards tokens * @param _beneficiary Address of the beneficiary of rewards @@ -1013,7 +945,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Check if the caller is authorized to operate on behalf of + * @notice Check if the caller is authorized to operate on behalf of * an indexer (i.e. the caller is the indexer or an operator) * @param _indexer Indexer address * @return True if the caller is authorized to operate on behalf of the indexer @@ -1023,7 +955,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M } /** - * @dev Return the current state of an allocation + * @notice Return the current state of an allocation * @param _allocationID Allocation identifier * @return AllocationState enum with the state of the allocation */ diff --git a/packages/contracts/contracts/staking/StakingExtension.sol b/packages/contracts/contracts/staking/StakingExtension.sol index b06fbe894..8bde14add 100644 --- a/packages/contracts/contracts/staking/StakingExtension.sol +++ b/packages/contracts/contracts/staking/StakingExtension.sol @@ -3,20 +3,24 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { StakingV4Storage } from "./StakingStorage.sol"; -import { IStakingExtension } from "./IStakingExtension.sol"; +import { IStakingExtension } from "@graphprotocol/interfaces/contracts/contracts/staking/IStakingExtension.sol"; import { TokenUtils } from "../utils/TokenUtils.sol"; -import { IGraphToken } from "../token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol"; -import { IStakes } from "./libs/IStakes.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; import { Stakes } from "./libs/Stakes.sol"; -import { IStakingData } from "./IStakingData.sol"; +import { IStakingData } from "@graphprotocol/interfaces/contracts/contracts/staking/IStakingData.sol"; import { MathUtils } from "./libs/MathUtils.sol"; /** * @title StakingExtension contract - * @dev This contract provides the logic to manage delegations and other Staking + * @author Edge & Node + * @notice This contract provides the logic to manage delegations and other Staking * extension features (e.g. storage getters). It is meant to be called through delegatecall from the * Staking contract, and is only kept separate to keep the Staking contract size * within limits. @@ -44,12 +48,14 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi * initialize() function, so it uses the same access control check to ensure it is * being called by the Staking implementation as part of the proxy upgrade process. * @param _delegationUnbondingPeriod Delegation unbonding period in blocks + * @param _cooldownBlocks Deprecated parameter (no longer used) * @param _delegationRatio Delegation capacity multiplier (e.g. 10 means 10x the indexer stake) * @param _delegationTaxPercentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million */ function initialize( uint32 _delegationUnbondingPeriod, - uint32, //_cooldownBlocks, deprecated + // solhint-disable-next-line no-unused-vars + uint32 _cooldownBlocks, // deprecated uint32 _delegationRatio, uint32 _delegationTaxPercentage ) external onlyImpl { @@ -59,38 +65,28 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @notice Set a delegation tax percentage to burn when delegated funds are deposited. - * @dev This function is only callable by the governor - * @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million + * @inheritdoc IStakingExtension */ function setDelegationTaxPercentage(uint32 _percentage) external override onlyGovernor { _setDelegationTaxPercentage(_percentage); } /** - * @notice 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 - * @dev This function is only callable by the governor - * @param _delegationRatio Delegation capacity multiplier + * @inheritdoc IStakingExtension */ function setDelegationRatio(uint32 _delegationRatio) external override onlyGovernor { _setDelegationRatio(_delegationRatio); } /** - * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. - * @dev This function is only callable by the governor - * @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + * @inheritdoc IStakingExtension */ function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) external override onlyGovernor { _setDelegationUnbondingPeriod(_delegationUnbondingPeriod); } /** - * @notice 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 + * @inheritdoc IStakingExtension */ function setSlasher(address _slasher, bool _allowed) external override onlyGovernor { require(_slasher != address(0), "!slasher"); @@ -99,10 +95,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @notice Delegate tokens to an indexer. - * @param _indexer Address of the indexer to which tokens are delegated - * @param _tokens Amount of tokens to delegate - * @return Amount of shares issued from the delegation pool + * @inheritdoc IStakingExtension */ function delegate(address _indexer, uint256 _tokens) external override notPartialPaused returns (uint256) { address delegator = msg.sender; @@ -115,32 +108,21 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period. - * @param _indexer Address of the indexer to which 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 + * @inheritdoc IStakingExtension */ function undelegate(address _indexer, uint256 _shares) external override notPartialPaused returns (uint256) { return _undelegate(msg.sender, _indexer, _shares); } /** - * @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally - * re-delegate to a new indexer. - * @param _indexer Withdraw available tokens delegated to indexer - * @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + * @inheritdoc IStakingExtension */ function withdrawDelegated(address _indexer, address _newIndexer) external override notPaused returns (uint256) { return _withdrawDelegated(msg.sender, _indexer, _newIndexer); } /** - * @notice Slash the indexer stake. Delegated tokens are not subject to slashing. - * @dev Can only be called by the slasher role. - * @param _indexer Address of indexer to slash - * @param _tokens Amount of tokens to slash from the indexer stake - * @param _reward Amount of reward tokens to send to a beneficiary - * @param _beneficiary Address of a beneficiary to receive a reward for the slashing + * @inheritdoc IStakingExtension */ function slash( address _indexer, @@ -188,48 +170,35 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @notice 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 + * @inheritdoc IStakingExtension */ function getDelegation(address _indexer, address _delegator) external view override returns (Delegation memory) { return __delegationPools[_indexer].delegators[_delegator]; } /** - * @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier: - * If delegation ratio is 100, and an Indexer has staked 5 GRT, - * then they can use up to 500 GRT from the delegated stake - * @return Delegation ratio + * @inheritdoc IStakingExtension */ function delegationRatio() external view override returns (uint32) { return __delegationRatio; } /** - * @notice Getter for delegationUnbondingPeriod: - * Time in epochs a delegator needs to wait to withdraw delegated stake - * @return Delegation unbonding period in epochs + * @inheritdoc IStakingExtension */ function delegationUnbondingPeriod() external view override returns (uint32) { return __delegationUnbondingPeriod; } /** - * @notice Getter for delegationTaxPercentage: - * Percentage of tokens to tax a delegation deposit, expressed in parts per million - * @return Delegation tax percentage in parts per million + * @inheritdoc IStakingExtension */ function delegationTaxPercentage() external view override returns (uint32) { return __delegationTaxPercentage; } /** - * @notice Getter for delegationPools[_indexer]: - * gets the delegation pool structure for a particular indexer. - * @param _indexer Address of the indexer for which to query the delegation pool - * @return Delegation pool as a DelegationPoolReturn struct + * @inheritdoc IStakingExtension */ function delegationPools(address _indexer) external view override returns (DelegationPoolReturn memory) { DelegationPool storage pool = __delegationPools[_indexer]; @@ -245,120 +214,91 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @notice Getter for rewardsDestination[_indexer]: - * returns the address where the indexer's rewards are sent. - * @param _indexer The indexer address for which to query the rewards destination - * @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked + * @inheritdoc IStakingExtension */ function rewardsDestination(address _indexer) external view override returns (address) { return __rewardsDestination[_indexer]; } /** - * @notice Getter for operatorAuth[_indexer][_maybeOperator]: - * returns true if the operator is authorized to operate on behalf of the indexer. - * @param _indexer The indexer address for which to query authorization - * @param _maybeOperator The address that may or may not be an operator - * @return True if the operator is authorized to operate on behalf of the indexer + * @inheritdoc IStakingExtension */ function operatorAuth(address _indexer, address _maybeOperator) external view override returns (bool) { return __operatorAuth[_indexer][_maybeOperator]; } /** - * @notice Getter for subgraphAllocations[_subgraphDeploymentId]: - * returns the amount of tokens allocated to a subgraph deployment. - * @param _subgraphDeploymentId The subgraph deployment for which to query the allocations - * @return The amount of tokens allocated to the subgraph deployment + * @inheritdoc IStakingExtension */ function subgraphAllocations(bytes32 _subgraphDeploymentId) external view override returns (uint256) { return __subgraphAllocations[_subgraphDeploymentId]; } /** - * @notice Getter for slashers[_maybeSlasher]: - * returns true if the address is a slasher, i.e. an entity that can slash indexers - * @param _maybeSlasher Address for which to check the slasher role - * @return True if the address is a slasher + * @inheritdoc IStakingExtension */ function slashers(address _maybeSlasher) external view override returns (bool) { return __slashers[_maybeSlasher]; } /** - * @notice Getter for minimumIndexerStake: the minimum - * amount of GRT that an indexer needs to stake. - * @return Minimum indexer stake in GRT + * @inheritdoc IStakingExtension */ function minimumIndexerStake() external view override returns (uint256) { return __minimumIndexerStake; } /** - * @notice Getter for thawingPeriod: the time in blocks an - * indexer needs to wait to unstake tokens. - * @return Thawing period in blocks + * @inheritdoc IStakingExtension */ function thawingPeriod() external view override returns (uint32) { return __thawingPeriod; } /** - * @notice Getter for curationPercentage: the percentage of - * query fees that are distributed to curators. - * @return Curation percentage in parts per million + * @inheritdoc IStakingExtension */ function curationPercentage() external view override returns (uint32) { return __curationPercentage; } /** - * @notice Getter for protocolPercentage: the percentage of - * query fees that are burned as protocol fees. - * @return Protocol percentage in parts per million + * @inheritdoc IStakingExtension */ function protocolPercentage() external view override returns (uint32) { return __protocolPercentage; } /** - * @notice Getter for maxAllocationEpochs: the maximum time in epochs - * that an allocation can be open before anyone is allowed to close it. This - * also caps the effective allocation when sending the allocation's query fees - * to the rebate pool. - * @return Maximum allocation period in epochs + * @inheritdoc IStakingExtension */ function maxAllocationEpochs() external view override returns (uint32) { return __maxAllocationEpochs; } /** - * @notice Getter for the numerator of the rebates alpha parameter - * @return Alpha numerator + * @inheritdoc IStakingExtension */ function alphaNumerator() external view override returns (uint32) { return __alphaNumerator; } /** - * @notice Getter for the denominator of the rebates alpha parameter - * @return Alpha denominator + * @inheritdoc IStakingExtension */ function alphaDenominator() external view override returns (uint32) { return __alphaDenominator; } /** - * @notice Getter for the numerator of the rebates lambda parameter - * @return Lambda numerator + * @inheritdoc IStakingExtension */ function lambdaNumerator() external view override returns (uint32) { return __lambdaNumerator; } /** - * @notice Getter for the denominator of the rebates lambda parameter - * @return Lambda denominator + * @inheritdoc IStakingExtension */ function lambdaDenominator() external view override returns (uint32) { return __lambdaDenominator; @@ -369,35 +309,28 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi * gets the stake information for an indexer as an IStakes.Indexer struct. * @param _indexer Indexer address for which to query the stake information * @return Stake information for the specified indexer, as an IStakes.Indexer struct + * @inheritdoc IStakingExtension */ function stakes(address _indexer) external view override returns (IStakes.Indexer memory) { return __stakes[_indexer]; } /** - * @notice Getter for allocations[_allocationID]: - * gets an allocation's information as an IStakingData.Allocation struct. - * @param _allocationID Allocation ID for which to query the allocation information - * @return The specified allocation, as an IStakingData.Allocation struct + * @inheritdoc IStakingExtension */ function allocations(address _allocationID) external view override returns (IStakingData.Allocation memory) { return __allocations[_allocationID]; } /** - * @notice 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 has tokens delegated to the indexer + * @inheritdoc IStakingExtension */ function isDelegator(address _indexer, address _delegator) public view override returns (bool) { return __delegationPools[_indexer].delegators[_delegator].shares > 0; } /** - * @notice 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 + * @inheritdoc IStakingExtension */ function getWithdraweableDelegatedTokens(Delegation memory _delegation) public view override returns (uint256) { // There must be locked tokens and period passed @@ -409,7 +342,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Internal: Set a delegation tax percentage to burn when delegated funds are deposited. + * @notice 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 { @@ -420,7 +353,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Internal: Set the delegation ratio. + * @notice 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 @@ -431,7 +364,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Internal: Set the period for undelegation of stake from indexer. + * @notice 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 { @@ -441,7 +374,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Delegate tokens to an indexer. + * @notice 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 @@ -480,7 +413,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Undelegate tokens from an indexer. + * @notice 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 @@ -531,7 +464,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Withdraw delegated tokens once the unbonding period has passed. + * @notice 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 @@ -570,7 +503,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi } /** - * @dev Collect tax to burn for an amount of tokens. + * @notice 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 diff --git a/packages/contracts/contracts/staking/StakingStorage.sol b/packages/contracts/contracts/staking/StakingStorage.sol index 949a63614..24f46009f 100644 --- a/packages/contracts/contracts/staking/StakingStorage.sol +++ b/packages/contracts/contracts/staking/StakingStorage.sol @@ -1,19 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// solhint-disable one-contract-per-file pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable one-contract-per-file, max-states-count +// solhint-disable named-parameters-mapping + import { Managed } from "../governance/Managed.sol"; -import { IStakingData } from "./IStakingData.sol"; -import { IStakes } from "./libs/IStakes.sol"; +import { IStakingData } from "@graphprotocol/interfaces/contracts/contracts/staking/IStakingData.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; /** * @title StakingV1Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the Staking contract, version 1 * @dev Note that we use a double underscore prefix for variable names; this prefix identifies * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. */ -// solhint-disable-next-line max-states-count contract StakingV1Storage is Managed { // -- Staking -- @@ -54,7 +59,7 @@ contract StakingV1Storage is Managed { /// @dev Subgraph Allocations: subgraphDeploymentID => tokens mapping(bytes32 => uint256) internal __subgraphAllocations; - // Rebate pools : epoch => Pool + /// @dev Deprecated rebate pools mapping (no longer used) mapping(uint256 => uint256) private __DEPRECATED_rebates; // solhint-disable-line var-name-mixedcase // -- Slashing -- @@ -95,6 +100,7 @@ contract StakingV1Storage is Managed { /** * @title StakingV2Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the Staking contract, version 2 * @dev Note that we use a double underscore prefix for variable names; this prefix identifies * variables that used to be public but are now internal, getters can be found on StakingExtension.sol. @@ -106,6 +112,7 @@ contract StakingV2Storage is StakingV1Storage { /** * @title StakingV3Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the base Staking contract, version 3. */ contract StakingV3Storage is StakingV2Storage { @@ -117,13 +124,15 @@ contract StakingV3Storage is StakingV2Storage { /** * @title StakingV4Storage + * @author Edge & Node * @notice This contract holds all the storage variables for the base Staking contract, version 4. * @dev Note that it includes a storage gap - if adding future versions, make sure to move the gap * to the new version and reduce the size of the gap accordingly. */ contract StakingV4Storage is StakingV3Storage { - // Additional rebate parameters for exponential rebates + /// @dev Numerator for the lambda parameter in exponential rebate calculations uint32 internal __lambdaNumerator; + /// @dev Denominator for the lambda parameter in exponential rebate calculations uint32 internal __lambdaDenominator; /// @dev Gap to allow adding variables in future upgrades (since L1Staking and L2Staking can have their own storage as well) diff --git a/packages/contracts/contracts/staking/libs/Exponential.sol b/packages/contracts/contracts/staking/libs/Exponential.sol index c9370342e..2b0222daa 100644 --- a/packages/contracts/contracts/staking/libs/Exponential.sol +++ b/packages/contracts/contracts/staking/libs/Exponential.sol @@ -6,13 +6,14 @@ import { LibFixedMath } from "./LibFixedMath.sol"; /** * @title LibExponential library + * @author Edge & Node * @notice A library to compute query fee rebates using an exponential formula */ library LibExponential { /// @dev Maximum value of the exponent for which to compute the exponential before clamping to zero. uint32 private constant MAX_EXPONENT = 15; - /// @dev The exponential formula used to compute fee-based rewards for + /// @notice The exponential formula used to compute fee-based rewards for /// staking pools in a given epoch. This function does not perform /// bounds checking on the inputs, but the following conditions /// need to be true: diff --git a/packages/contracts/contracts/staking/libs/IStakes.sol b/packages/contracts/contracts/staking/libs/IStakes.sol deleted file mode 100644 index 701336409..000000000 --- a/packages/contracts/contracts/staking/libs/IStakes.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; -pragma abicoder v2; - -interface IStakes { - struct Indexer { - uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) - uint256 tokensAllocated; // Tokens used in allocations - uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period - uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn - } -} diff --git a/packages/contracts/contracts/staking/libs/LibFixedMath.sol b/packages/contracts/contracts/staking/libs/LibFixedMath.sol index ae8c9b69e..55628ea6e 100644 --- a/packages/contracts/contracts/staking/libs/LibFixedMath.sol +++ b/packages/contracts/contracts/staking/libs/LibFixedMath.sol @@ -20,36 +20,49 @@ pragma solidity ^0.7.6; -// solhint-disable indent -/// @dev Signed, fixed-point, 127-bit precision math library. +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + +/** + * @title LibFixedMath + * @author Edge & Node + * @notice Signed, fixed-point, 127-bit precision math library + */ library LibFixedMath { - // 1 + /// @dev Fixed-point representation of 1 int256 private constant FIXED_1 = int256(0x0000000000000000000000000000000080000000000000000000000000000000); - // 2**255 + /// @dev Minimum fixed-point value (2**255) int256 private constant MIN_FIXED_VAL = int256(0x8000000000000000000000000000000000000000000000000000000000000000); - // 1^2 (in fixed-point) + /// @dev Fixed-point representation of 1^2 int256 private constant FIXED_1_SQUARED = int256(0x4000000000000000000000000000000000000000000000000000000000000000); - // 1 + /// @dev Maximum value for natural logarithm calculation int256 private constant LN_MAX_VAL = FIXED_1; - // e ^ -63.875 + /// @dev Minimum value for natural logarithm calculation (e ^ -63.875) int256 private constant LN_MIN_VAL = int256(0x0000000000000000000000000000000000000000000000000000000733048c5a); - // 0 + /// @dev Maximum value for exponentiation calculation int256 private constant EXP_MAX_VAL = 0; - // -63.875 + /// @dev Minimum value for exponentiation calculation (-63.875) int256 private constant EXP_MIN_VAL = -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000); - /// @dev Get one as a fixed-point number. + /// @notice Get one as a fixed-point number. + /// @return f The fixed-point representation of 1 function one() internal pure returns (int256 f) { f = FIXED_1; } - /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + /// @notice Returns the addition of two fixed point numbers, reverting on overflow. + /// @param a First fixed-point number + /// @param b Second fixed-point number + /// @return c The sum of a and b function add(int256 a, int256 b) internal pure returns (int256 c) { c = _add(a, b); } - /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + /// @notice Returns the subtraction of two fixed point numbers, reverting on overflow. + /// @param a First fixed-point number + /// @param b Second fixed-point number + /// @return c The difference a - b function sub(int256 a, int256 b) internal pure returns (int256 c) { if (b == MIN_FIXED_VAL) { revert("out-of-bounds"); @@ -57,24 +70,37 @@ library LibFixedMath { c = _add(a, -b); } - /// @dev Returns the multiplication of two fixed point numbers, reverting on overflow. + /// @notice Returns the multiplication of two fixed point numbers, reverting on overflow. + /// @param a First fixed-point number + /// @param b Second fixed-point number + /// @return c The product of a and b function mul(int256 a, int256 b) internal pure returns (int256 c) { c = _mul(a, b) / FIXED_1; } - /// @dev Returns the division of two fixed point numbers. + /// @notice Returns the division of two fixed point numbers. + /// @param a Dividend fixed-point number + /// @param b Divisor fixed-point number + /// @return c The quotient a / b function div(int256 a, int256 b) internal pure returns (int256 c) { c = _div(_mul(a, FIXED_1), b); } - /// @dev Performs (a * n) / d, without scaling for precision. + /// @notice Performs (a * n) / d, without scaling for precision. + /// @param a First operand + /// @param n Numerator + /// @param d Denominator + /// @return c The result of (a * n) / d function mulDiv(int256 a, int256 n, int256 d) internal pure returns (int256 c) { c = _div(_mul(a, n), d); } - /// @dev Returns the unsigned integer result of multiplying a fixed-point + /// @notice Returns the unsigned integer result of multiplying a fixed-point /// number with an integer, reverting if the multiplication overflows. /// Negative results are clamped to zero. + /// @param f Fixed-point number + /// @param u Unsigned integer + /// @return The result of f * u as an unsigned integer function uintMul(int256 f, uint256 u) internal pure returns (uint256) { if (int256(u) < int256(0)) { revert("out-of-bounds"); @@ -86,7 +112,9 @@ library LibFixedMath { return uint256(uint256(c) >> 127); } - /// @dev Returns the absolute value of a fixed point number. + /// @notice Returns the absolute value of a fixed point number. + /// @param f Fixed-point number + /// @return c The absolute value of f function abs(int256 f) internal pure returns (int256 c) { if (f == MIN_FIXED_VAL) { revert("out-of-bounds"); @@ -98,23 +126,32 @@ library LibFixedMath { } } - /// @dev Returns 1 / `x`, where `x` is a fixed-point number. + /// @notice Returns 1 / `x`, where `x` is a fixed-point number. + /// @param f Fixed-point number to invert + /// @return c The reciprocal of f function invert(int256 f) internal pure returns (int256 c) { c = _div(FIXED_1_SQUARED, f); } - /// @dev Convert signed `n` / 1 to a fixed-point number. + /// @notice Convert signed `n` / 1 to a fixed-point number. + /// @param n Signed integer to convert + /// @return f The fixed-point representation of n function toFixed(int256 n) internal pure returns (int256 f) { f = _mul(n, FIXED_1); } - /// @dev Convert signed `n` / `d` to a fixed-point number. + /// @notice Convert signed `n` / `d` to a fixed-point number. + /// @param n Numerator + /// @param d Denominator + /// @return f The fixed-point representation of n/d function toFixed(int256 n, int256 d) internal pure returns (int256 f) { f = _div(_mul(n, FIXED_1), d); } - /// @dev Convert unsigned `n` / 1 to a fixed-point number. + /// @notice Convert unsigned `n` / 1 to a fixed-point number. /// Reverts if `n` is too large to fit in a fixed-point number. + /// @param n Unsigned integer to convert + /// @return f The fixed-point representation of n function toFixed(uint256 n) internal pure returns (int256 f) { if (int256(n) < int256(0)) { revert("out-of-bounds"); @@ -122,8 +159,11 @@ library LibFixedMath { f = _mul(int256(n), FIXED_1); } - /// @dev Convert unsigned `n` / `d` to a fixed-point number. + /// @notice Convert unsigned `n` / `d` to a fixed-point number. /// Reverts if `n` / `d` is too large to fit in a fixed-point number. + /// @param n Numerator + /// @param d Denominator + /// @return f The fixed-point representation of n/d function toFixed(uint256 n, uint256 d) internal pure returns (int256 f) { if (int256(n) < int256(0)) { revert("out-of-bounds"); @@ -134,12 +174,16 @@ library LibFixedMath { f = _div(_mul(int256(n), FIXED_1), int256(d)); } - /// @dev Convert a fixed-point number to an integer. + /// @notice Convert a fixed-point number to an integer. + /// @param f Fixed-point number to convert + /// @return n The integer representation of f function toInteger(int256 f) internal pure returns (int256 n) { return f / FIXED_1; } - /// @dev Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL + /// @notice Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL + /// @param x Fixed-point number to compute logarithm of + /// @return r The natural logarithm of x function ln(int256 x) internal pure returns (int256 r) { if (x > LN_MAX_VAL) { revert("out-of-bounds"); @@ -228,7 +272,9 @@ library LibFixedMath { r += (z * (0x088888888888888888888888888888888 - y)) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16 } - /// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + /// @notice Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + /// @param x Fixed-point number to compute exponent of + /// @return r The natural exponent of x function exp(int256 x) internal pure returns (int256 r) { if (x < EXP_MIN_VAL) { // Saturate to zero below EXP_MIN_VAL. @@ -350,7 +396,10 @@ library LibFixedMath { } } - /// @dev Returns the multiplication two numbers, reverting on overflow. + /// @notice Returns the multiplication two numbers, reverting on overflow. + /// @param a First operand + /// @param b Second operand + /// @return c The product of a and b function _mul(int256 a, int256 b) private pure returns (int256 c) { if (a == 0 || b == 0) { return 0; @@ -361,7 +410,10 @@ library LibFixedMath { } } - /// @dev Returns the division of two numbers, reverting on division by zero. + /// @notice Returns the division of two numbers, reverting on division by zero. + /// @param a Dividend + /// @param b Divisor + /// @return c The quotient of a and b function _div(int256 a, int256 b) private pure returns (int256 c) { if (b == 0) { revert("overflow"); @@ -372,7 +424,10 @@ library LibFixedMath { c = a / b; } - /// @dev Adds two numbers, reverting on overflow. + /// @notice Adds two numbers, reverting on overflow. + /// @param a First operand + /// @param b Second operand + /// @return c The sum of a and b function _add(int256 a, int256 b) private pure returns (int256 c) { c = a + b; if ((a < 0 && b < 0 && c > a) || (a > 0 && b > 0 && c < a)) { diff --git a/packages/contracts/contracts/staking/libs/MathUtils.sol b/packages/contracts/contracts/staking/libs/MathUtils.sol index 0fb20389a..467e1ae2a 100644 --- a/packages/contracts/contracts/staking/libs/MathUtils.sol +++ b/packages/contracts/contracts/staking/libs/MathUtils.sol @@ -2,17 +2,21 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts/math/SafeMath.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; /** * @title MathUtils Library + * @author Edge & Node * @notice A collection of functions to perform math operations */ library MathUtils { using SafeMath for uint256; /** - * @dev Calculates the weighted average of two values pondering each of these + * @notice Calculates the weighted average of two values pondering each of these * values based on configured weights. The contribution of each value N is * weightN/(weightA + weightB). The calculation rounds up to ensure the result * is always greater than the smallest of the two values. @@ -20,6 +24,7 @@ library MathUtils { * @param weightA The weight to use for value A * @param valueB The amount for value B * @param weightB The weight to use for value B + * @return The weighted average of the two values, rounded up */ function weightedAverageRoundingUp( uint256 valueA, @@ -31,14 +36,20 @@ library MathUtils { } /** - * @dev Returns the minimum of two numbers. + * @notice Returns the minimum of two numbers. + * @param x First number + * @param y Second number + * @return The smaller of the two numbers */ function min(uint256 x, uint256 y) internal pure returns (uint256) { return x <= y ? x : y; } /** - * @dev Returns the difference between two numbers or zero if negative. + * @notice Returns the difference between two numbers or zero if negative. + * @param x First number + * @param y Second number + * @return The difference x - y, or 0 if y > x */ function diffOrZero(uint256 x, uint256 y) internal pure returns (uint256) { return (x > y) ? x.sub(y) : 0; diff --git a/packages/contracts/contracts/staking/libs/Stakes.sol b/packages/contracts/contracts/staking/libs/Stakes.sol index b09101032..459a82996 100644 --- a/packages/contracts/contracts/staking/libs/Stakes.sol +++ b/packages/contracts/contracts/staking/libs/Stakes.sol @@ -3,13 +3,15 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "@openzeppelin/contracts/math/SafeMath.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import "./MathUtils.sol"; -import "./IStakes.sol"; +import { MathUtils } from "./MathUtils.sol"; +import { IStakes } from "@graphprotocol/interfaces/contracts/contracts/staking/libs/IStakes.sol"; /** * @title A collection of data structures and functions to manage the Indexer Stake state. + * @author Edge & Node + * @notice A collection of data structures and functions to manage the Indexer Stake state. * Used for low-level state changes, require() conditions should be evaluated * at the caller function scope. */ @@ -18,7 +20,7 @@ library Stakes { using Stakes for IStakes.Indexer; /** - * @dev Deposit tokens to the indexer stake. + * @notice Deposit tokens to the indexer stake. * @param stake Stake data * @param _tokens Amount of tokens to deposit */ @@ -27,7 +29,7 @@ library Stakes { } /** - * @dev Release tokens from the indexer stake. + * @notice Release tokens from the indexer stake. * @param stake Stake data * @param _tokens Amount of tokens to release */ @@ -36,7 +38,7 @@ library Stakes { } /** - * @dev Allocate tokens from the main stack to a SubgraphDeployment. + * @notice Allocate tokens from the main stack to a SubgraphDeployment. * @param stake Stake data * @param _tokens Amount of tokens to allocate */ @@ -45,7 +47,7 @@ library Stakes { } /** - * @dev Unallocate tokens from a SubgraphDeployment back to the main stack. + * @notice Unallocate tokens from a SubgraphDeployment back to the main stack. * @param stake Stake data * @param _tokens Amount of tokens to unallocate */ @@ -54,7 +56,7 @@ library Stakes { } /** - * @dev Lock tokens until a thawing period pass. + * @notice Lock tokens until a thawing period pass. * @param stake Stake data * @param _tokens Amount of tokens to unstake * @param _period Period in blocks that need to pass before withdrawal @@ -77,7 +79,7 @@ library Stakes { } /** - * @dev Unlock tokens. + * @notice Unlock tokens. * @param stake Stake data * @param _tokens Amount of tokens to unlock */ @@ -89,7 +91,7 @@ library Stakes { } /** - * @dev Take all tokens out from the locked stake for withdrawal. + * @notice Take all tokens out from the locked stake for withdrawal. * @param stake Stake data * @return Amount of tokens being withdrawn */ @@ -109,7 +111,7 @@ library Stakes { } /** - * @dev Return the amount of tokens used in allocations and locked for withdrawal. + * @notice Return the amount of tokens used in allocations and locked for withdrawal. * @param stake Stake data * @return Token amount */ @@ -118,7 +120,7 @@ library Stakes { } /** - * @dev Return the amount of tokens staked not considering the ones that are already going + * @notice Return the amount of tokens staked not considering the ones that are already going * through the thawing period or are ready for withdrawal. We call it secure stake because * it is not subject to change by a withdraw call from the indexer. * @param stake Stake data @@ -129,7 +131,7 @@ library Stakes { } /** - * @dev Tokens free balance on the indexer stake that can be used for any purpose. + * @notice Tokens free balance on the indexer stake that can be used for any purpose. * Any token that is allocated cannot be used as well as tokens that are going through the * thawing period or are withdrawable * Calc: tokensStaked - tokensAllocated - tokensLocked @@ -141,7 +143,7 @@ library Stakes { } /** - * @dev Tokens free balance on the indexer stake that can be used for allocations. + * @notice Tokens free balance on the indexer stake that can be used for allocations. * This function accepts a parameter for extra delegated capacity that takes into * account delegated tokens * @param stake Stake data @@ -171,7 +173,7 @@ library Stakes { } /** - * @dev Tokens available for withdrawal after thawing period. + * @notice Tokens available for withdrawal after thawing period. * @param stake Stake data * @return Token amount */ diff --git a/packages/contracts/contracts/tests/CallhookReceiverMock.sol b/packages/contracts/contracts/tests/CallhookReceiverMock.sol index e2418f3c8..f6b9d130e 100644 --- a/packages/contracts/contracts/tests/CallhookReceiverMock.sol +++ b/packages/contracts/contracts/tests/CallhookReceiverMock.sol @@ -2,21 +2,29 @@ pragma solidity ^0.7.6; -import "../gateway/ICallhookReceiver.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, use-natspec + +import { ICallhookReceiver } from "@graphprotocol/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol"; /** - * @title GovernedMock contract + * @title CallhookReceiverMock contract + * @dev Mock contract for testing callhook receiver functionality */ contract CallhookReceiverMock is ICallhookReceiver { + /** + * @dev Emitted when a transfer is received + * @param from Address that sent the transfer + * @param amount Amount of tokens transferred + * @param foo First test parameter + * @param bar Second test parameter + */ event TransferReceived(address from, uint256 amount, uint256 foo, uint256 bar); /** - * @dev Receive tokens with a callhook from the bridge - * Expects two uint256 values encoded in _data. + * @inheritdoc ICallhookReceiver + * @dev Expects two uint256 values encoded in _data. * Reverts if the first of these values is zero. - * @param _from Token sender in L1 - * @param _amount Amount of tokens that were transferred - * @param _data ABI-encoded callhook data */ function onTokenTransfer(address _from, uint256 _amount, bytes calldata _data) external override { uint256 foo; diff --git a/packages/contracts/contracts/tests/GovernedMock.sol b/packages/contracts/contracts/tests/GovernedMock.sol index cc908287b..9e6c2cc18 100644 --- a/packages/contracts/contracts/tests/GovernedMock.sol +++ b/packages/contracts/contracts/tests/GovernedMock.sol @@ -2,12 +2,19 @@ pragma solidity ^0.7.6; -import "../governance/Governed.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { Governed } from "../governance/Governed.sol"; /** * @title GovernedMock contract + * @dev Mock contract for testing Governed functionality */ contract GovernedMock is Governed { + /** + * @dev Constructor that initializes the contract with the deployer as governor + */ constructor() { Governed._initialize(msg.sender); } diff --git a/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolBadMock.sol b/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolBadMock.sol index f540b1b96..12b06b332 100644 --- a/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolBadMock.sol +++ b/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolBadMock.sol @@ -3,17 +3,37 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; +// solhint-disable named-parameters-mapping +// solhint-disable gas-small-strings + +/** + * @title L1GraphTokenLockTransferToolBadMock + * @author Edge & Node + * @notice Mock contract for testing L1 Graph Token Lock Transfer Tool with bad behavior + */ contract L1GraphTokenLockTransferToolBadMock { + /** + * @notice Mapping from L1 wallet address to L2 wallet address + */ mapping(address => address) public l2WalletAddress; - function setL2WalletAddress(address _l1Address, address _l2Address) external { - l2WalletAddress[_l1Address] = _l2Address; + /** + * @notice Set the L2 wallet address for an L1 wallet + * @param l1Address L1 wallet address + * @param l2Address L2 wallet address + */ + function setL2WalletAddress(address l1Address, address l2Address) external { + l2WalletAddress[l1Address] = l2Address; } - // Sends 1 wei less than requested - function pullETH(address _l1Wallet, uint256 _amount) external { - require(l2WalletAddress[_l1Wallet] != address(0), "L1GraphTokenLockTransferToolMock: unknown L1 wallet"); - (bool success, ) = payable(msg.sender).call{ value: _amount - 1 }(""); + /** + * @notice Pull ETH from the contract to the caller (sends 1 wei less than requested for testing) + * @param l1Wallet L1 wallet address to check + * @param amount Amount of ETH to pull + */ + function pullETH(address l1Wallet, uint256 amount) external { + require(l2WalletAddress[l1Wallet] != address(0), "L1GraphTokenLockTransferToolMock: unknown L1 wallet"); + (bool success, ) = payable(msg.sender).call{ value: amount - 1 }(""); require(success, "L1GraphTokenLockTransferToolMock: ETH pull failed"); } } diff --git a/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolMock.sol b/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolMock.sol index a1321d62f..92e8f73f7 100644 --- a/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolMock.sol +++ b/packages/contracts/contracts/tests/L1GraphTokenLockTransferToolMock.sol @@ -3,16 +3,37 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; +// solhint-disable named-parameters-mapping +// solhint-disable gas-small-strings + +/** + * @title L1GraphTokenLockTransferToolMock + * @author Edge & Node + * @notice Mock contract for testing L1 Graph Token Lock Transfer Tool functionality + */ contract L1GraphTokenLockTransferToolMock { + /** + * @notice Mapping from L1 wallet address to L2 wallet address + */ mapping(address => address) public l2WalletAddress; - function setL2WalletAddress(address _l1Address, address _l2Address) external { - l2WalletAddress[_l1Address] = _l2Address; + /** + * @notice Set the L2 wallet address for an L1 wallet + * @param l1Address L1 wallet address + * @param l2Address L2 wallet address + */ + function setL2WalletAddress(address l1Address, address l2Address) external { + l2WalletAddress[l1Address] = l2Address; } - function pullETH(address _l1Wallet, uint256 _amount) external { - require(l2WalletAddress[_l1Wallet] != address(0), "L1GraphTokenLockTransferToolMock: unknown L1 wallet"); - (bool success, ) = payable(msg.sender).call{ value: _amount }(""); + /** + * @notice Pull ETH from the contract to the caller + * @param l1Wallet L1 wallet address to check + * @param amount Amount of ETH to pull + */ + function pullETH(address l1Wallet, uint256 amount) external { + require(l2WalletAddress[l1Wallet] != address(0), "L1GraphTokenLockTransferToolMock: unknown L1 wallet"); + (bool success, ) = payable(msg.sender).call{ value: amount }(""); require(success, "L1GraphTokenLockTransferToolMock: ETH pull failed"); } } diff --git a/packages/contracts/contracts/tests/LegacyGNSMock.sol b/packages/contracts/contracts/tests/LegacyGNSMock.sol index b2b4088b9..30f98ea31 100644 --- a/packages/contracts/contracts/tests/LegacyGNSMock.sol +++ b/packages/contracts/contracts/tests/LegacyGNSMock.sol @@ -3,8 +3,11 @@ pragma solidity ^0.7.6; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + import { L1GNS } from "../discovery/L1GNS.sol"; -import { IGNS } from "../discovery/IGNS.sol"; +import { IGNS } from "@graphprotocol/interfaces/contracts/contracts/discovery/IGNS.sol"; /** * @title LegacyGNSMock contract diff --git a/packages/contracts/contracts/tests/MockERC165.sol b/packages/contracts/contracts/tests/MockERC165.sol new file mode 100644 index 000000000..056493fd3 --- /dev/null +++ b/packages/contracts/contracts/tests/MockERC165.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity 0.7.6; + +import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol"; + +/** + * @title MockERC165 + * @author Edge & Node + * @dev Minimal implementation of IERC165 for testing + * @notice Used to test interface validation - supports only ERC165, not specific interfaces + */ +contract MockERC165 is IERC165 { + /** + * @inheritdoc IERC165 + */ + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} diff --git a/packages/contracts/contracts/tests/MockIssuanceAllocator.sol b/packages/contracts/contracts/tests/MockIssuanceAllocator.sol new file mode 100644 index 000000000..ba1f8f2bd --- /dev/null +++ b/packages/contracts/contracts/tests/MockIssuanceAllocator.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +// solhint-disable gas-increment-by-one, gas-indexed-events, named-parameters-mapping, use-natspec + +pragma solidity 0.7.6; +pragma abicoder v2; + +import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol"; +import { TargetIssuancePerBlock } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol"; +import { IIssuanceAllocationDistribution } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol"; +import { IIssuanceTarget } from "@graphprotocol/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol"; + +/** + * @title MockIssuanceAllocator + * @dev A simple mock contract for the IssuanceAllocator interfaces used by RewardsManager. + */ +contract MockIssuanceAllocator is IERC165, IIssuanceAllocationDistribution { + /// @dev Mapping to store TargetIssuancePerBlock for each target + mapping(address => TargetIssuancePerBlock) private _targetIssuance; + + /** + * @dev Call beforeIssuanceAllocationChange on a target + * @param target The target contract address + */ + function callBeforeIssuanceAllocationChange(address target) external { + IIssuanceTarget(target).beforeIssuanceAllocationChange(); + } + + /** + * @inheritdoc IIssuanceAllocationDistribution + */ + function getTargetIssuancePerBlock(address target) external view override returns (TargetIssuancePerBlock memory) { + return _targetIssuance[target]; + } + + /** + * @inheritdoc IIssuanceAllocationDistribution + * @dev Mock always returns current block number + */ + function distributeIssuance() external view override returns (uint256) { + return block.number; + } + + /** + * @dev Set target issuance directly for testing + * @param target The target contract address + * @param allocatorIssuance The allocator issuance per block + * @param selfIssuance The self issuance per block + * @param callBefore Whether to call beforeIssuanceAllocationChange on the target + */ + function setTargetAllocation( + address target, + uint256 allocatorIssuance, + uint256 selfIssuance, + bool callBefore + ) external { + if (callBefore) { + IIssuanceTarget(target).beforeIssuanceAllocationChange(); + } + _targetIssuance[target] = TargetIssuancePerBlock({ + allocatorIssuancePerBlock: allocatorIssuance, + allocatorIssuanceBlockAppliedTo: block.number, + selfIssuancePerBlock: selfIssuance, + selfIssuanceBlockAppliedTo: block.number + }); + } + + /** + * @inheritdoc IERC165 + */ + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + return + interfaceId == type(IIssuanceAllocationDistribution).interfaceId || + interfaceId == type(IERC165).interfaceId; + } +} diff --git a/packages/contracts/contracts/tests/MockRewardsEligibilityOracle.sol b/packages/contracts/contracts/tests/MockRewardsEligibilityOracle.sol new file mode 100644 index 000000000..6b13d4d76 --- /dev/null +++ b/packages/contracts/contracts/tests/MockRewardsEligibilityOracle.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +// solhint-disable named-parameters-mapping + +pragma solidity 0.7.6; + +import { IRewardsEligibility } from "@graphprotocol/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol"; +import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol"; + +/** + * @title MockRewardsEligibilityOracle + * @author Edge & Node + * @notice A simple mock contract for the RewardsEligibilityOracle interface + * @dev A simple mock contract for the RewardsEligibilityOracle interface + */ +contract MockRewardsEligibilityOracle is IRewardsEligibility, IERC165 { + /// @dev Mapping to store eligibility status for each indexer + mapping(address => bool) private eligible; + + /// @dev Mapping to track which indexers have been explicitly set + mapping(address => bool) private isSet; + + /// @dev Default response for indexers not explicitly set + bool private defaultResponse; + + /** + * @notice Constructor + * @param newDefaultResponse Default response for isEligible + */ + constructor(bool newDefaultResponse) { + defaultResponse = newDefaultResponse; + } + + /** + * @notice Set whether a specific indexer is eligible + * @param indexer The indexer address + * @param eligibility Whether the indexer is eligible + */ + function setIndexerEligible(address indexer, bool eligibility) external { + eligible[indexer] = eligibility; + isSet[indexer] = true; + } + + /** + * @notice Set the default response for indexers not explicitly set + * @param newDefaultResponse The default response + */ + function setDefaultResponse(bool newDefaultResponse) external { + defaultResponse = newDefaultResponse; + } + + /** + * @inheritdoc IRewardsEligibility + */ + function isEligible(address indexer) external view override returns (bool) { + // If the indexer has been explicitly set, return that value + if (isSet[indexer]) { + return eligible[indexer]; + } + + // Otherwise return the default response + return defaultResponse; + } + + /** + * @inheritdoc IERC165 + */ + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + return interfaceId == type(IRewardsEligibility).interfaceId || interfaceId == type(IERC165).interfaceId; + } +} diff --git a/packages/contracts/contracts/tests/MockSubgraphService.sol b/packages/contracts/contracts/tests/MockSubgraphService.sol new file mode 100644 index 000000000..703edd010 --- /dev/null +++ b/packages/contracts/contracts/tests/MockSubgraphService.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +// solhint-disable named-parameters-mapping + +pragma solidity 0.7.6; + +import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol"; + +/** + * @title MockSubgraphService + * @author Edge & Node + * @notice A mock contract for testing SubgraphService as a rewards issuer + * @dev Implements IRewardsIssuer interface to simulate SubgraphService behavior in tests + */ +contract MockSubgraphService is IRewardsIssuer { + /// @dev Struct to store allocation data + struct Allocation { + bool isActive; + address indexer; + bytes32 subgraphDeploymentId; + uint256 tokens; + uint256 accRewardsPerAllocatedToken; + uint256 accRewardsPending; + } + + /// @dev Mapping of allocation ID to allocation data + mapping(address => Allocation) private allocations; + + /// @dev Mapping of subgraph deployment ID to total allocated tokens + mapping(bytes32 => uint256) private subgraphAllocatedTokens; + + /** + * @notice Set allocation data for testing + * @param allocationId The allocation ID + * @param isActive Whether the allocation is active + * @param indexer The indexer address + * @param subgraphDeploymentId The subgraph deployment ID + * @param tokens Amount of allocated tokens + * @param accRewardsPerAllocatedToken Rewards snapshot + * @param accRewardsPending Accumulated rewards pending + */ + function setAllocation( + address allocationId, + bool isActive, + address indexer, + bytes32 subgraphDeploymentId, + uint256 tokens, + uint256 accRewardsPerAllocatedToken, + uint256 accRewardsPending + ) external { + allocations[allocationId] = Allocation({ + isActive: isActive, + indexer: indexer, + subgraphDeploymentId: subgraphDeploymentId, + tokens: tokens, + accRewardsPerAllocatedToken: accRewardsPerAllocatedToken, + accRewardsPending: accRewardsPending + }); + } + + /** + * @notice Set total allocated tokens for a subgraph + * @param subgraphDeploymentId The subgraph deployment ID + * @param tokens Total tokens allocated + */ + function setSubgraphAllocatedTokens(bytes32 subgraphDeploymentId, uint256 tokens) external { + subgraphAllocatedTokens[subgraphDeploymentId] = tokens; + } + + /** + * @inheritdoc IRewardsIssuer + */ + function getAllocationData( + address allocationId + ) + external + view + override + returns ( + bool isActive, + address indexer, + bytes32 subgraphDeploymentId, + uint256 tokens, + uint256 accRewardsPerAllocatedToken, + uint256 accRewardsPending + ) + { + Allocation memory allocation = allocations[allocationId]; + return ( + allocation.isActive, + allocation.indexer, + allocation.subgraphDeploymentId, + allocation.tokens, + allocation.accRewardsPerAllocatedToken, + allocation.accRewardsPending + ); + } + + /** + * @inheritdoc IRewardsIssuer + */ + function getSubgraphAllocatedTokens(bytes32 subgraphDeploymentId) external view override returns (uint256) { + return subgraphAllocatedTokens[subgraphDeploymentId]; + } +} diff --git a/packages/contracts/contracts/tests/arbitrum/ArbSysMock.sol b/packages/contracts/contracts/tests/arbitrum/ArbSysMock.sol index b5af6114e..3c256fa74 100644 --- a/packages/contracts/contracts/tests/arbitrum/ArbSysMock.sol +++ b/packages/contracts/contracts/tests/arbitrum/ArbSysMock.sol @@ -2,12 +2,22 @@ pragma solidity ^0.7.6; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + /** * @title ArbSys Mock Contract * @dev This is a mock implementation of the ArbSys precompiled contract used in Arbitrum * It's used for testing the L2GraphTokenGateway contract */ contract ArbSysMock { + /** + * @dev Emitted when a transaction is sent from L2 to L1 + * @param from Address sending the transaction on L2 + * @param to Address receiving the transaction on L1 + * @param id Unique identifier for the L2-to-L1 transaction + * @param data Transaction data + */ event L2ToL1Tx(address indexed from, address indexed to, uint256 indexed id, bytes data); /** diff --git a/packages/contracts/contracts/tests/arbitrum/BridgeMock.sol b/packages/contracts/contracts/tests/arbitrum/BridgeMock.sol index 77be89b4e..9bcc71982 100644 --- a/packages/contracts/contracts/tests/arbitrum/BridgeMock.sol +++ b/packages/contracts/contracts/tests/arbitrum/BridgeMock.sol @@ -2,29 +2,35 @@ pragma solidity ^0.7.6; -import "../../arbitrum/IBridge.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, use-natspec + +import { IBridge } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IBridge.sol"; /** * @title Arbitrum Bridge mock contract * @dev This contract implements Arbitrum's IBridge interface for testing purposes */ contract BridgeMock is IBridge { - // Address of the (mock) Arbitrum Inbox + /** + * @notice Address of the (mock) Arbitrum Inbox + */ address public inbox; - // Address of the (mock) Arbitrum Outbox + /** + * @notice Address of the (mock) Arbitrum Outbox + */ address public outbox; - // Index of the next message on the inbox messages array + /** + * @notice Index of the next message on the inbox messages array + */ uint256 public messageIndex; - // Inbox messages array + /** + * @inheritdoc IBridge + */ bytes32[] public override inboxAccs; /** - * @dev Deliver a message to the inbox. The encoded message will be - * added to the inbox array, and messageIndex will be incremented. - * @param _kind Type of the message - * @param _sender Address that is sending the message - * @param _messageDataHash keccak256 hash of the message data - * @return The next index for the inbox array + * @inheritdoc IBridge */ function deliverMessageToInbox( uint8 _kind, @@ -38,13 +44,7 @@ contract BridgeMock is IBridge { } /** - * @dev Executes an L1 function call incoing from L2. This can only be called - * by the Outbox. - * @param _destAddr Contract to call - * @param _amount ETH value to send - * @param _data Calldata for the function call - * @return True if the call was successful, false otherwise - * @return Return data from the call + * @inheritdoc IBridge */ function executeCall( address _destAddr, @@ -62,9 +62,7 @@ contract BridgeMock is IBridge { } /** - * @dev Set the address of the inbox. Anyone can call this, because it's a mock. - * @param _inbox Address of the inbox - * @param _enabled Enable the inbox (ignored) + * @inheritdoc IBridge */ function setInbox(address _inbox, bool _enabled) external override { inbox = _inbox; @@ -72,9 +70,7 @@ contract BridgeMock is IBridge { } /** - * @dev Set the address of the outbox. Anyone can call this, because it's a mock. - * @param _outbox Address of the outbox - * @param _enabled Enable the outbox (ignored) + * @inheritdoc IBridge */ function setOutbox(address _outbox, bool _enabled) external override { outbox = _outbox; @@ -84,33 +80,28 @@ contract BridgeMock is IBridge { // View functions /** - * @dev Getter for the active outbox (in this case there's only one) + * @inheritdoc IBridge */ function activeOutbox() external view override returns (address) { return outbox; } /** - * @dev Getter for whether an address is an allowed inbox (in this case there's only one) - * @param _inbox Address to check - * @return True if the address is the allowed inbox, false otherwise + * @inheritdoc IBridge */ function allowedInboxes(address _inbox) external view override returns (bool) { return _inbox == inbox; } /** - * @dev Getter for whether an address is an allowed outbox (in this case there's only one) - * @param _outbox Address to check - * @return True if the address is the allowed outbox, false otherwise + * @inheritdoc IBridge */ function allowedOutboxes(address _outbox) external view override returns (bool) { return _outbox == outbox; } /** - * @dev Getter for the count of messages in the inboxAccs - * @return Number of messages in inboxAccs + * @inheritdoc IBridge */ function messageCount() external view override returns (uint256) { return inboxAccs.length; diff --git a/packages/contracts/contracts/tests/arbitrum/InboxMock.sol b/packages/contracts/contracts/tests/arbitrum/InboxMock.sol index 57af6941c..38cee3b44 100644 --- a/packages/contracts/contracts/tests/arbitrum/InboxMock.sol +++ b/packages/contracts/contracts/tests/arbitrum/InboxMock.sol @@ -2,26 +2,28 @@ pragma solidity ^0.7.6; -import "../../arbitrum/IInbox.sol"; -import "../../arbitrum/AddressAliasHelper.sol"; +import { IInbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IInbox.sol"; +import { AddressAliasHelper } from "../../arbitrum/AddressAliasHelper.sol"; +import { IBridge } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IBridge.sol"; /** * @title Arbitrum Inbox mock contract - * @dev This contract implements (a subset of) Arbitrum's IInbox interface for testing purposes + * @author Edge & Node + * @notice This contract implements (a subset of) Arbitrum's IInbox interface for testing purposes */ contract InboxMock is IInbox { - // Type indicator for a standard L2 message + /// @dev Type indicator for a standard L2 message uint8 internal constant L2_MSG = 3; - // Type indicator for a retryable ticket message + /// @dev Type indicator for a retryable ticket message // solhint-disable-next-line const-name-snakecase uint8 internal constant L1MessageType_submitRetryableTx = 9; - // Address of the Bridge (mock) contract + /** + * @inheritdoc IInbox + */ IBridge public override bridge; /** - * @dev Send a message to L2 (by delivering it to the Bridge) - * @param _messageData Encoded data to send in the message - * @return message number returned by the inbox + * @inheritdoc IInbox */ function sendL2Message(bytes calldata _messageData) external override returns (uint256) { uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(_messageData)); @@ -30,7 +32,7 @@ contract InboxMock is IInbox { } /** - * @dev Set the address of the (mock) bridge + * @notice Set the address of the (mock) bridge * @param _bridge Address of the bridge */ function setBridge(address _bridge) external { @@ -38,6 +40,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function sendUnsignedTransaction( @@ -52,6 +55,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function sendContractTransaction( @@ -65,6 +69,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function sendL1FundedUnsignedTransaction( @@ -78,6 +83,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function sendL1FundedContractTransaction( @@ -90,16 +96,7 @@ contract InboxMock is IInbox { } /** - * @dev Creates a retryable ticket for an L2 transaction - * @param _destAddr Address of the contract to call in L2 - * @param _arbTxCallValue Callvalue to use in the L2 transaction - * @param _maxSubmissionCost Max cost of submitting the ticket, in Wei - * @param _submissionRefundAddress L2 address to refund for any remaining value from the submission cost - * @param _valueRefundAddress L2 address to refund if the ticket times out or gets cancelled - * @param _maxGas Max gas for the L2 transcation - * @param _gasPriceBid Gas price bid on L2 - * @param _data Encoded calldata for the L2 transaction (including function selector) - * @return message number returned by the bridge + * @inheritdoc IInbox */ function createRetryableTicket( address _destAddr, @@ -132,11 +129,16 @@ contract InboxMock is IInbox { ); } + /** + * @inheritdoc IInbox + * @dev Unimplemented in this mock + */ function depositEth(uint256) external payable override returns (uint256) { revert("Unimplemented"); } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function pauseCreateRetryables() external pure override { @@ -144,6 +146,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function unpauseCreateRetryables() external pure override { @@ -151,6 +154,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function startRewriteAddress() external pure override { @@ -158,6 +162,7 @@ contract InboxMock is IInbox { } /** + * @inheritdoc IInbox * @dev Unimplemented in this mock */ function stopRewriteAddress() external pure override { @@ -165,7 +170,7 @@ contract InboxMock is IInbox { } /** - * @dev Deliver a message to the bridge + * @notice Deliver a message to the bridge * @param _kind Type of the message * @param _sender Address that is sending the message * @param _messageData Encoded message data @@ -178,7 +183,7 @@ contract InboxMock is IInbox { } /** - * @dev Deliver a message to the bridge + * @notice Deliver a message to the bridge * @param _kind Type of the message * @param _sender Address that is sending the message * @param _messageDataHash keccak256 hash of the encoded message data diff --git a/packages/contracts/contracts/tests/arbitrum/OutboxMock.sol b/packages/contracts/contracts/tests/arbitrum/OutboxMock.sol index 92b9bb246..7a8e9932b 100644 --- a/packages/contracts/contracts/tests/arbitrum/OutboxMock.sol +++ b/packages/contracts/contracts/tests/arbitrum/OutboxMock.sol @@ -2,15 +2,26 @@ pragma solidity ^0.7.6; -import "../../arbitrum/IOutbox.sol"; -import "../../arbitrum/IBridge.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { IOutbox } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IOutbox.sol"; +import { IBridge } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/IBridge.sol"; /** * @title Arbitrum Outbox mock contract * @dev This contract implements (a subset of) Arbitrum's IOutbox interface for testing purposes */ contract OutboxMock is IOutbox { - // Context of an L2-to-L1 function call + /** + * @dev Context of an L2-to-L1 function call + * @param l2Block L2 block number + * @param l1Block L1 block number + * @param timestamp Timestamp of the call + * @param batchNum Batch number + * @param outputId Output ID + * @param sender Address of the sender + */ struct L2ToL1Context { uint128 l2Block; uint128 l1Block; @@ -19,7 +30,7 @@ contract OutboxMock is IOutbox { bytes32 outputId; address sender; } - // Context of the current L2-to-L1 function call (set and cleared in each transaction) + /// @dev Context of the current L2-to-L1 function call (set and cleared in each transaction) L2ToL1Context internal context; // Address of the (mock) Arbitrum Bridge @@ -33,59 +44,42 @@ contract OutboxMock is IOutbox { bridge = IBridge(_bridge); } - /** - * @dev Getter for the L2 sender of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1Sender() external view override returns (address) { return context.sender; } - /** - * @dev Getter for the L2 block of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1Block() external view override returns (uint256) { return context.l2Block; } - /** - * @dev Getter for the L1 block of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1EthBlock() external view override returns (uint256) { return context.l1Block; } - /** - * @dev Getter for the L1 timestamp of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1Timestamp() external view override returns (uint256) { return context.timestamp; } - /** - * @dev Getter for the L2 batch number of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1BatchNum() external view override returns (uint256) { return context.batchNum; } - /** - * @dev Getter for the output ID of the current incoming message - */ + /// @inheritdoc IOutbox function l2ToL1OutputId() external view override returns (bytes32) { return context.outputId; } - /** - * @dev Unimplemented in this mock - */ + /// @inheritdoc IOutbox function processOutgoingMessages(bytes calldata, uint256[] calldata) external pure override { revert("Unimplemented"); } - /** - * @dev Check whether an outbox entry for a message exists. - * This mock returns always true. - */ + /// @inheritdoc IOutbox function outboxEntryExists(uint256) external pure override returns (bool) { return true; } diff --git a/packages/contracts/contracts/tests/ens/IENS.sol b/packages/contracts/contracts/tests/ens/IENS.sol index f03cb651c..042a9170f 100644 --- a/packages/contracts/contracts/tests/ens/IENS.sol +++ b/packages/contracts/contracts/tests/ens/IENS.sol @@ -1,9 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + pragma solidity ^0.7.6; // Needed for abi and typechain in the npm package +/** + * @title ENS Registry Interface + * @author Edge & Node + * @notice Interface for the Ethereum Name Service registry + */ interface IENS { + /** + * @notice Get the owner of a node + * @param node The node to query + * @return The address of the owner + */ function owner(bytes32 node) external view returns (address); - // Must call setRecord, not setOwner, We must namehash it ourselves as well - function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external; + /** + * @notice Set the record for a subnode + * @dev Must call setRecord, not setOwner. We must namehash it ourselves as well. + * @param node The parent node + * @param label The label hash of the subnode + * @param _owner The address of the new owner + * @param resolver The address of the resolver + * @param ttl The TTL in seconds + */ + function setSubnodeRecord(bytes32 node, bytes32 label, address _owner, address resolver, uint64 ttl) external; } diff --git a/packages/contracts/contracts/tests/ens/IPublicResolver.sol b/packages/contracts/contracts/tests/ens/IPublicResolver.sol index 06ce2243b..7a449fb8d 100644 --- a/packages/contracts/contracts/tests/ens/IPublicResolver.sol +++ b/packages/contracts/contracts/tests/ens/IPublicResolver.sol @@ -1,8 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + pragma solidity ^0.7.6; // Needed for abi and typechain in the npm package +/** + * @title ENS Public Resolver Interface + * @author Edge & Node + * @notice Interface for the ENS public resolver contract + */ interface IPublicResolver { + /** + * @notice Get the text record for a node + * @param node The node to query + * @param key The key of the text record + * @return The text record value + */ function text(bytes32 node, string calldata key) external view returns (string memory); + /** + * @notice Set the text record for a node + * @param node The node to set the record for + * @param key The key of the text record + * @param value The value to set + */ function setText(bytes32 node, string calldata key, string calldata value) external; } diff --git a/packages/contracts/contracts/tests/ens/ITestRegistrar.sol b/packages/contracts/contracts/tests/ens/ITestRegistrar.sol index 8a795cc85..406a27fb7 100644 --- a/packages/contracts/contracts/tests/ens/ITestRegistrar.sol +++ b/packages/contracts/contracts/tests/ens/ITestRegistrar.sol @@ -1,5 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + pragma solidity ^0.7.6; +/** + * @title Test Registrar Interface + * @author Edge & Node + * @notice Interface for a test ENS registrar contract + */ interface ITestRegistrar { + /** + * @notice Register a name with the registrar + * @param label The label hash to register + * @param owner The address to assign ownership to + */ function register(bytes32 label, address owner) external; } diff --git a/packages/contracts/contracts/token/GraphToken.sol b/packages/contracts/contracts/token/GraphToken.sol index 53496b9a5..652fa5477 100644 --- a/packages/contracts/contracts/token/GraphToken.sol +++ b/packages/contracts/contracts/token/GraphToken.sol @@ -2,16 +2,21 @@ pragma solidity ^0.7.6; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; -import "@openzeppelin/contracts/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-small-strings, gas-strict-inequalities +// solhint-disable named-parameters-mapping -import "../governance/Governed.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; +import { ECDSA } from "@openzeppelin/contracts/cryptography/ECDSA.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { Governed } from "../governance/Governed.sol"; /** * @title GraphToken contract - * @dev This is the implementation of the ERC20 Graph Token. + * @author Edge & Node + * @notice This is the implementation of the ERC20 Graph Token. * The implementation exposes a Permit() function to allow for a spender to send a signed message * and approve funds to a spender following EIP2612 to make integration with other contracts easier. * @@ -28,32 +33,53 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { // -- EIP712 -- // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator + /// @dev EIP-712 domain type hash for signature verification bytes32 private constant DOMAIN_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + /// @dev EIP-712 domain name hash for signature verification bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Token"); + /// @dev EIP-712 domain version hash for signature verification bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); + /// @dev EIP-712 domain salt for signature verification (randomly generated) bytes32 private constant DOMAIN_SALT = 0x51f3d585afe6dfeb2af01bba0889a36c1db03beec88c6a4d0c53817069026afa; // Randomly generated salt + /// @dev EIP-712 permit typehash for signature verification bytes32 private constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); // -- State -- - bytes32 private DOMAIN_SEPARATOR; + /// @dev EIP-712 domain separator for signature verification + bytes32 private domainSeparator; + /// @dev Mapping of addresses authorized to mint tokens mapping(address => bool) private _minters; + /** + * @notice Nonces for permit functionality (EIP-2612) + * @dev Mapping from owner address to current nonce for permit signatures + */ mapping(address => uint256) public nonces; // -- Events -- + /** + * @notice Emitted when a new minter is added + * @param account Address of the minter that was added + */ event MinterAdded(address indexed account); + + /** + * @notice Emitted when a minter is removed + * @param account Address of the minter that was removed + */ event MinterRemoved(address indexed account); + /// @dev Modifier to restrict access to minters only modifier onlyMinter() { require(isMinter(msg.sender), "Only minter can call"); _; } /** - * @dev Graph Token Contract Constructor. + * @notice Graph Token Contract Constructor. * @param _initialSupply Initial supply of GRT */ constructor(uint256 _initialSupply) ERC20("Graph Token", "GRT") { @@ -66,7 +92,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { _addMinter(msg.sender); // EIP-712 domain separator - DOMAIN_SEPARATOR = keccak256( + domainSeparator = keccak256( abi.encode( DOMAIN_TYPE_HASH, DOMAIN_NAME_HASH, @@ -79,7 +105,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Approve token allowance by validating a message signed by the holder. + * @notice Approve token allowance by validating a message signed by the holder. * @param _owner Address of the token holder * @param _spender Address of the approved spender * @param _value Amount of tokens to approve the spender @@ -100,7 +126,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", - DOMAIN_SEPARATOR, + domainSeparator, keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonces[_owner], _deadline)) ) ); @@ -114,7 +140,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Add a new minter. + * @notice Add a new minter. * @param _account Address of the minter */ function addMinter(address _account) external onlyGovernor { @@ -122,7 +148,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Remove a minter. + * @notice Remove a minter. * @param _account Address of the minter */ function removeMinter(address _account) external onlyGovernor { @@ -130,14 +156,14 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Renounce to be a minter. + * @notice Renounce to be a minter. */ function renounceMinter() external { _removeMinter(msg.sender); } /** - * @dev Mint new tokens. + * @notice Mint new tokens. * @param _to Address to send the newly minted tokens * @param _amount Amount of tokens to mint */ @@ -146,7 +172,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Return if the `_account` is a minter or not. + * @notice Return if the `_account` is a minter or not. * @param _account Address to check * @return True if the `_account` is minter */ @@ -155,7 +181,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Add a new minter. + * @notice Add a new minter. * @param _account Address of the minter */ function _addMinter(address _account) private { @@ -164,7 +190,7 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Remove a minter. + * @notice Remove a minter. * @param _account Address of the minter */ function _removeMinter(address _account) private { @@ -173,11 +199,12 @@ contract GraphToken is Governed, ERC20, ERC20Burnable { } /** - * @dev Get the running network chain ID. + * @notice Get the running network chain ID. * @return The chain ID */ function _getChainID() private pure returns (uint256) { uint256 id; + // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } diff --git a/packages/contracts/contracts/token/IGraphToken.sol b/packages/contracts/contracts/token/IGraphToken.sol deleted file mode 100644 index df3b7643f..000000000 --- a/packages/contracts/contracts/token/IGraphToken.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface IGraphToken is IERC20 { - // -- Mint and Burn -- - - function burn(uint256 amount) external; - - function burnFrom(address _from, uint256 amount) external; - - function mint(address _to, uint256 _amount) external; - - // -- Mint Admin -- - - function addMinter(address _account) external; - - function removeMinter(address _account) external; - - function renounceMinter() external; - - function isMinter(address _account) external view returns (bool); - - // -- Permit -- - - function permit( - address _owner, - address _spender, - uint256 _value, - uint256 _deadline, - uint8 _v, - bytes32 _r, - bytes32 _s - ) external; - - // -- Allowance -- - - function increaseAllowance(address spender, uint256 addedValue) external returns (bool); - - function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); -} diff --git a/packages/contracts/contracts/upgrades/GraphProxy.sol b/packages/contracts/contracts/upgrades/GraphProxy.sol index d6fbfac7f..b787b476a 100644 --- a/packages/contracts/contracts/upgrades/GraphProxy.sol +++ b/packages/contracts/contracts/upgrades/GraphProxy.sol @@ -2,13 +2,19 @@ pragma solidity ^0.7.6 || 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-small-strings + +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + import { GraphProxyStorage } from "./GraphProxyStorage.sol"; -import { IGraphProxy } from "./IGraphProxy.sol"; +import { IGraphProxy } from "@graphprotocol/interfaces/contracts/contracts/upgrades/IGraphProxy.sol"; /** * @title Graph Proxy - * @dev Graph Proxy contract used to delegate call implementation contracts and support upgrades. + * @author Edge & Node + * @notice 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 @@ -69,56 +75,49 @@ contract GraphProxy is GraphProxyStorage, IGraphProxy { } /** - * @notice Get the current admin - * + * @inheritdoc IGraphProxy * @dev 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` - * - * @return The address of the current admin */ - function admin() external override ifAdminOrPendingImpl returns (address) { + function admin() external override ifAdminOrPendingImpl returns (address adminAddress) { return _getAdmin(); } /** - * @notice Get the current implementation. - * + * @inheritdoc IGraphProxy * @dev 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` - * - * @return The address of the current implementation for this proxy */ - function implementation() external override ifAdminOrPendingImpl returns (address) { + function implementation() external override ifAdminOrPendingImpl returns (address implementationAddress) { return _getImplementation(); } /** - * @notice Get the current pending implementation. - * + * @inheritdoc IGraphProxy * @dev 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` - * - * @return The address of the current pending implementation for this proxy */ - function pendingImplementation() external override ifAdminOrPendingImpl returns (address) { + function pendingImplementation() + external + override + ifAdminOrPendingImpl + returns (address pendingImplementationAddress) + { return _getPendingImplementation(); } /** - * @notice Changes the admin of the proxy. - * + * @inheritdoc IGraphProxy * @dev NOTE: Only the admin can call this function. - * - * @param _newAdmin Address of the new admin */ function setAdmin(address _newAdmin) external override ifAdmin { require(_newAdmin != address(0), "Admin cant be the zero address"); @@ -126,25 +125,22 @@ contract GraphProxy is GraphProxyStorage, IGraphProxy { } /** - * @notice Upgrades to a new implementation contract. + * @inheritdoc IGraphProxy * @dev NOTE: Only the admin can call this function. - * @param _newImplementation Address of implementation contract */ function upgradeTo(address _newImplementation) external override ifAdmin { _setPendingImplementation(_newImplementation); } /** - * @notice Admin function for new implementation to accept its role as implementation. + * @inheritdoc IGraphProxy */ function acceptUpgrade() external override ifAdminOrPendingImpl { _acceptUpgrade(); } /** - * @notice Admin function for new implementation to accept its role as implementation, - * calling a function on the new implementation. - * @param data Calldata (including selector) for the function to delegatecall into the implementation + * @inheritdoc IGraphProxy */ function acceptUpgradeAndCall(bytes calldata data) external override ifAdminOrPendingImpl { _acceptUpgrade(); @@ -154,7 +150,7 @@ contract GraphProxy is GraphProxyStorage, IGraphProxy { } /** - * @dev Admin function for new implementation to accept its role as implementation. + * @notice Admin function for new implementation to accept its role as implementation. */ function _acceptUpgrade() internal { address _pendingImplementation = _getPendingImplementation(); @@ -166,7 +162,7 @@ contract GraphProxy is GraphProxyStorage, IGraphProxy { } /** - * @dev Delegates the current call to implementation. + * @notice Delegates the current call to implementation. * This function does not return to its internal call site, it will return directly to the * external caller. */ diff --git a/packages/contracts/contracts/upgrades/GraphProxyAdmin.sol b/packages/contracts/contracts/upgrades/GraphProxyAdmin.sol index db8e9dcb3..97f0b2e11 100644 --- a/packages/contracts/contracts/upgrades/GraphProxyAdmin.sol +++ b/packages/contracts/contracts/upgrades/GraphProxyAdmin.sol @@ -2,14 +2,17 @@ pragma solidity ^0.7.6 || 0.8.27; +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + import { Governed } from "../governance/Governed.sol"; -import { IGraphProxy } from "./IGraphProxy.sol"; +import { IGraphProxy } from "@graphprotocol/interfaces/contracts/contracts/upgrades/IGraphProxy.sol"; import { GraphUpgradeable } from "./GraphUpgradeable.sol"; /** * @title GraphProxyAdmin - * @dev This is the owner of upgradeable proxy contracts. + * @author Edge & Node + * @notice 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. diff --git a/packages/contracts/contracts/upgrades/GraphProxyStorage.sol b/packages/contracts/contracts/upgrades/GraphProxyStorage.sol index 7871e4996..828af8e23 100644 --- a/packages/contracts/contracts/upgrades/GraphProxyStorage.sol +++ b/packages/contracts/contracts/upgrades/GraphProxyStorage.sol @@ -2,9 +2,12 @@ pragma solidity ^0.7.6 || 0.8.27; +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + /** * @title Graph Proxy Storage - * @dev Contract functions related to getting and setting proxy storage. + * @author Edge & Node + * @notice 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. */ @@ -32,7 +35,9 @@ abstract contract GraphProxyStorage { bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** - * @dev Emitted when pendingImplementation is changed. + * @notice Emitted when pendingImplementation is changed. + * @param oldPendingImplementation Address of the previous pending implementation + * @param newPendingImplementation Address of the new pending implementation */ event PendingImplementationUpdated( address indexed oldPendingImplementation, @@ -40,13 +45,17 @@ abstract contract GraphProxyStorage { ); /** - * @dev Emitted when pendingImplementation is accepted, + * @notice Emitted when pendingImplementation is accepted, * which means contract implementation is updated. + * @param oldImplementation Address of the previous implementation + * @param newImplementation Address of the new implementation */ event ImplementationUpdated(address indexed oldImplementation, address indexed newImplementation); /** - * @dev Emitted when the admin account has changed. + * @notice Emitted when the admin account has changed. + * @param oldAdmin Address of the previous admin + * @param newAdmin Address of the new admin */ event AdminUpdated(address indexed oldAdmin, address indexed newAdmin); @@ -59,6 +68,7 @@ abstract contract GraphProxyStorage { } /** + * @notice Returns the current admin address * @return adm The admin slot. */ function _getAdmin() internal view returns (address adm) { @@ -70,7 +80,7 @@ abstract contract GraphProxyStorage { } /** - * @dev Sets the address of the proxy admin. + * @notice Sets the address of the proxy admin. * @param _newAdmin Address of the new proxy admin */ function _setAdmin(address _newAdmin) internal { @@ -85,7 +95,7 @@ abstract contract GraphProxyStorage { } /** - * @dev Returns the current implementation. + * @notice Returns the current implementation. * @return impl Address of the current implementation */ function _getImplementation() internal view returns (address impl) { @@ -97,7 +107,7 @@ abstract contract GraphProxyStorage { } /** - * @dev Returns the current pending implementation. + * @notice Returns the current pending implementation. * @return impl Address of the current pending implementation */ function _getPendingImplementation() internal view returns (address impl) { @@ -109,7 +119,7 @@ abstract contract GraphProxyStorage { } /** - * @dev Sets the implementation address of the proxy. + * @notice Sets the implementation address of the proxy. * @param _newImplementation Address of the new implementation */ function _setImplementation(address _newImplementation) internal { @@ -125,7 +135,7 @@ abstract contract GraphProxyStorage { } /** - * @dev Sets the pending implementation address of the proxy. + * @notice Sets the pending implementation address of the proxy. * @param _newImplementation Address of the new pending implementation */ function _setPendingImplementation(address _newImplementation) internal { diff --git a/packages/contracts/contracts/upgrades/GraphUpgradeable.sol b/packages/contracts/contracts/upgrades/GraphUpgradeable.sol index 60dfbe888..827082213 100644 --- a/packages/contracts/contracts/upgrades/GraphUpgradeable.sol +++ b/packages/contracts/contracts/upgrades/GraphUpgradeable.sol @@ -2,11 +2,14 @@ pragma solidity ^0.7.6 || 0.8.27; -import { IGraphProxy } from "./IGraphProxy.sol"; +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + +import { IGraphProxy } from "@graphprotocol/interfaces/contracts/contracts/upgrades/IGraphProxy.sol"; /** * @title Graph Upgradeable - * @dev This contract is intended to be inherited from upgradeable contracts. + * @author Edge & Node + * @notice This contract is intended to be inherited from upgradeable contracts. */ abstract contract GraphUpgradeable { /** @@ -18,6 +21,7 @@ abstract contract GraphUpgradeable { /** * @dev Check if the caller is the proxy admin. + * @param _proxy The proxy contract to check admin for */ modifier onlyProxyAdmin(IGraphProxy _proxy) { require(msg.sender == _proxy.admin(), "Caller must be the proxy admin"); @@ -33,7 +37,7 @@ abstract contract GraphUpgradeable { } /** - * @dev Returns the current implementation. + * @notice Returns the current implementation. * @return impl Address of the current implementation */ function _implementation() internal view returns (address impl) { diff --git a/packages/contracts/contracts/upgrades/IGraphProxy.sol b/packages/contracts/contracts/upgrades/IGraphProxy.sol deleted file mode 100644 index 4f501ed7c..000000000 --- a/packages/contracts/contracts/upgrades/IGraphProxy.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.7.6 || 0.8.27; - -interface IGraphProxy { - function admin() external returns (address); - - function setAdmin(address _newAdmin) external; - - function implementation() external returns (address); - - function pendingImplementation() external returns (address); - - function upgradeTo(address _newImplementation) external; - - function acceptUpgrade() external; - - function acceptUpgradeAndCall(bytes calldata data) external; -} diff --git a/packages/contracts/contracts/utils/TokenUtils.sol b/packages/contracts/contracts/utils/TokenUtils.sol index ef2f03211..b1c2290f6 100644 --- a/packages/contracts/contracts/utils/TokenUtils.sol +++ b/packages/contracts/contracts/utils/TokenUtils.sol @@ -2,17 +2,20 @@ pragma solidity ^0.7.6 || 0.8.27; -import { IGraphToken } from "../token/IGraphToken.sol"; +/* solhint-disable gas-custom-errors */ // Cannot use custom errors with 0.7.6 + +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; /** * @title TokenUtils library + * @author Edge & Node * @notice This library contains utility functions for handling tokens (transfers and burns). * It is specifically adapted for the GraphToken, so does not need to handle edge cases * for other tokens. */ library TokenUtils { /** - * @dev Pull tokens from an address to this contract. + * @notice Pull tokens from an address to this contract. * @param _graphToken Token to transfer * @param _from Address sending the tokens * @param _amount Amount of tokens to transfer @@ -24,7 +27,7 @@ library TokenUtils { } /** - * @dev Push tokens from this contract to a receiving address. + * @notice Push tokens from this contract to a receiving address. * @param _graphToken Token to transfer * @param _to Address receiving the tokens * @param _amount Amount of tokens to transfer @@ -36,7 +39,7 @@ library TokenUtils { } /** - * @dev Burn tokens held by this contract. + * @notice Burn tokens held by this contract. * @param _graphToken Token to burn * @param _amount Amount of tokens to burn */ diff --git a/packages/contracts/test/.solcover.js b/packages/contracts/test/.solcover.js index 7181b78fa..125581cd1 100644 --- a/packages/contracts/test/.solcover.js +++ b/packages/contracts/test/.solcover.js @@ -1,4 +1,4 @@ -const skipFiles = ['bancor', 'ens', 'erc1056', 'arbitrum', 'tests/arbitrum'] +const skipFiles = ['bancor', 'ens', 'erc1056', 'arbitrum', 'tests', '*Mock.sol'] module.exports = { providerOptions: { diff --git a/packages/contracts/test/hardhat.config.ts b/packages/contracts/test/hardhat.config.ts index 1d8ed1c58..9555c7c7f 100644 --- a/packages/contracts/test/hardhat.config.ts +++ b/packages/contracts/test/hardhat.config.ts @@ -5,6 +5,7 @@ import '@nomiclabs/hardhat-waffle' import '@typechain/hardhat' import 'dotenv/config' import 'hardhat-gas-reporter' +import 'hardhat-ignore-warnings' import 'solidity-coverage' // Test-specific tasks import './tasks/migrate/nitro' @@ -44,6 +45,18 @@ const config: HardhatUserConfig = { typechain: { outDir: 'types', }, + warnings: { + // Suppress warnings from legacy OpenZeppelin contracts and external dependencies + 'arbos-precompiles/**/*': { + default: 'off', + }, + '@openzeppelin/contracts/**/*': { + default: 'off', + }, + 'contracts/staking/StakingExtension.sol': { + 5667: 'off', // Unused function parameter + }, + }, defaultNetwork: 'hardhat', networks: { hardhat: { diff --git a/packages/contracts/test/tests/unit/rewards/rewards-calculations.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-calculations.test.ts new file mode 100644 index 000000000..b100905b0 --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-calculations.test.ts @@ -0,0 +1,389 @@ +import { Curation } from '@graphprotocol/contracts' +import { EpochManager } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { IStaking } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { + deriveChannelKey, + formatGRT, + GraphNetworkContracts, + helpers, + randomHexBytes, + toBN, + toGRT, +} from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { BigNumber as BN } from 'bignumber.js' +import { expect } from 'chai' +import { BigNumber, constants } from 'ethers' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +const { HashZero, WeiPerEther } = constants + +const toRound = (n: BigNumber) => formatGRT(n.add(toGRT('0.5'))).split('.')[0] + +describe('Rewards - Calculations', () => { + const graph = hre.graph() + let governor: SignerWithAddress + let curator1: SignerWithAddress + let curator2: SignerWithAddress + let indexer1: SignerWithAddress + let indexer2: SignerWithAddress + let assetHolder: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let epochManager: EpochManager + let staking: IStaking + let rewardsManager: RewardsManager + + // Derive some channel keys for each indexer used to sign attestations + const channelKey1 = deriveChannelKey() + + const subgraphDeploymentID1 = randomHexBytes() + const subgraphDeploymentID2 = randomHexBytes() + + const allocationID1 = channelKey1.address + + const metadata = HashZero + + const ISSUANCE_RATE_PERIODS = 4 // blocks required to issue 800 GRT rewards + const ISSUANCE_PER_BLOCK = toBN('200000000000000000000') // 200 GRT every block + + // Core formula that gets accumulated rewards per signal for a period of time + const getRewardsPerSignal = (k: BN, t: BN, s: BN): string => { + if (s.eq(0)) { + return '0' + } + return k.times(t).div(s).toPrecision(18).toString() + } + + // Tracks the accumulated rewards as totalSignalled or supply changes across snapshots + class RewardsTracker { + totalSignalled = BigNumber.from(0) + lastUpdatedBlock = 0 + accumulated = BigNumber.from(0) + + static async create() { + const tracker = new RewardsTracker() + await tracker.snapshot() + return tracker + } + + async snapshot() { + this.accumulated = this.accumulated.add(await this.accrued()) + this.totalSignalled = await grt.balanceOf(curation.address) + this.lastUpdatedBlock = await helpers.latestBlock() + return this + } + + async elapsedBlocks() { + const currentBlock = await helpers.latestBlock() + return currentBlock - this.lastUpdatedBlock + } + + async accrued() { + const nBlocks = await this.elapsedBlocks() + return this.accruedByElapsed(nBlocks) + } + + accruedByElapsed(nBlocks: BigNumber | number) { + const n = getRewardsPerSignal( + new BN(ISSUANCE_PER_BLOCK.toString()), + new BN(nBlocks.toString()), + new BN(this.totalSignalled.toString()), + ) + return toGRT(n) + } + } + + // Test accumulated rewards per signal + const shouldGetNewRewardsPerSignal = async (nBlocks = ISSUANCE_RATE_PERIODS) => { + // -- t0 -- + const tracker = await RewardsTracker.create() + + // Jump + await helpers.mine(nBlocks) + + // -- t1 -- + + // Contract calculation + const contractAccrued = await rewardsManager.getNewRewardsPerSignal() + // Local calculation + const expectedAccrued = await tracker.accrued() + + // Check + expect(toRound(expectedAccrued)).eq(toRound(contractAccrued)) + return expectedAccrued + } + + before(async function () { + const testAccounts = await graph.getTestAccounts() + ;[indexer1, indexer2, curator1, curator2, assetHolder] = testAccounts + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + epochManager = contracts.EpochManager + staking = contracts.Staking as IStaking + rewardsManager = contracts.RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [indexer1, indexer2, curator1, curator2, assetHolder]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(staking.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + context('issuing rewards', function () { + beforeEach(async function () { + // 5% minute rate (4 blocks) + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + }) + + describe('getNewRewardsPerSignal', function () { + it('accrued per signal when no tokens signalled', async function () { + // When there is no tokens signalled no rewards are accrued + await helpers.mineEpoch(epochManager) + const accrued = await rewardsManager.getNewRewardsPerSignal() + expect(accrued).eq(0) + }) + + it('accrued per signal when tokens signalled', async function () { + // Update total signalled + const tokensToSignal = toGRT('1000') + await curation.connect(curator1).mint(subgraphDeploymentID1, tokensToSignal, 0) + + // Check + await shouldGetNewRewardsPerSignal() + }) + + it('accrued per signal when signalled tokens w/ many subgraphs', async function () { + // Update total signalled + await curation.connect(curator1).mint(subgraphDeploymentID1, toGRT('1000'), 0) + + // Check + await shouldGetNewRewardsPerSignal() + + // Update total signalled + await curation.connect(curator2).mint(subgraphDeploymentID2, toGRT('250'), 0) + + // Check + await shouldGetNewRewardsPerSignal() + }) + }) + + describe('updateAccRewardsPerSignal', function () { + it('update the accumulated rewards per signal state', async function () { + // Update total signalled + await curation.connect(curator1).mint(subgraphDeploymentID1, toGRT('1000'), 0) + // Snapshot + const tracker = await RewardsTracker.create() + + // Update + await rewardsManager.connect(governor).updateAccRewardsPerSignal() + const contractAccrued = await rewardsManager.accRewardsPerSignal() + + // Check + const expectedAccrued = await tracker.accrued() + expect(toRound(expectedAccrued)).eq(toRound(contractAccrued)) + }) + + it('update the accumulated rewards per signal state after many blocks', async function () { + // Update total signalled + await curation.connect(curator1).mint(subgraphDeploymentID1, toGRT('1000'), 0) + // Snapshot + const tracker = await RewardsTracker.create() + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Update + await rewardsManager.connect(governor).updateAccRewardsPerSignal() + const contractAccrued = await rewardsManager.accRewardsPerSignal() + + // Check + const expectedAccrued = await tracker.accrued() + expect(toRound(expectedAccrued)).eq(toRound(contractAccrued)) + }) + }) + + describe('getAccRewardsForSubgraph', function () { + it('accrued for each subgraph', async function () { + // Curator1 - Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + const tracker1 = await RewardsTracker.create() + + // Curator2 - Update total signalled + const signalled2 = toGRT('500') + await curation.connect(curator2).mint(subgraphDeploymentID2, signalled2, 0) + + // Snapshot + const tracker2 = await RewardsTracker.create() + await tracker1.snapshot() + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Snapshot + await tracker1.snapshot() + await tracker2.snapshot() + + // Calculate rewards + const rewardsPerSignal1 = tracker1.accumulated + const rewardsPerSignal2 = tracker2.accumulated + const expectedRewardsSG1 = rewardsPerSignal1.mul(signalled1).div(WeiPerEther) + const expectedRewardsSG2 = rewardsPerSignal2.mul(signalled2).div(WeiPerEther) + + // Get rewards from contract + const contractRewardsSG1 = await rewardsManager.getAccRewardsForSubgraph(subgraphDeploymentID1) + const contractRewardsSG2 = await rewardsManager.getAccRewardsForSubgraph(subgraphDeploymentID2) + + // Check + expect(toRound(expectedRewardsSG1)).eq(toRound(contractRewardsSG1)) + expect(toRound(expectedRewardsSG2)).eq(toRound(contractRewardsSG2)) + }) + + it('should return zero rewards when subgraph signal is below minimum threshold', async function () { + // Set a high minimum signal threshold + const highMinimumSignal = toGRT('2000') + await rewardsManager.connect(governor).setMinimumSubgraphSignal(highMinimumSignal) + + // Signal less than the minimum threshold + const lowSignal = toGRT('1000') + await curation.connect(curator1).mint(subgraphDeploymentID1, lowSignal, 0) + + // Jump some blocks to potentially accrue rewards + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Check that no rewards are accrued due to minimum signal threshold + const contractRewards = await rewardsManager.getAccRewardsForSubgraph(subgraphDeploymentID1) + expect(contractRewards).eq(0) + }) + }) + + describe('onSubgraphSignalUpdate', function () { + it('update the accumulated rewards for subgraph state', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + // Snapshot + const tracker1 = await RewardsTracker.create() + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Update + await rewardsManager.connect(governor).onSubgraphSignalUpdate(subgraphDeploymentID1) + + // Check + const contractRewardsSG1 = (await rewardsManager.subgraphs(subgraphDeploymentID1)).accRewardsForSubgraph + const rewardsPerSignal1 = await tracker1.accrued() + const expectedRewardsSG1 = rewardsPerSignal1.mul(signalled1).div(WeiPerEther) + expect(toRound(expectedRewardsSG1)).eq(toRound(contractRewardsSG1)) + + const contractAccrued = await rewardsManager.accRewardsPerSignal() + const expectedAccrued = await tracker1.accrued() + expect(toRound(expectedAccrued)).eq(toRound(contractAccrued)) + + const contractBlockUpdated = await rewardsManager.accRewardsPerSignalLastBlockUpdated() + const expectedBlockUpdated = await helpers.latestBlock() + expect(expectedBlockUpdated).eq(contractBlockUpdated) + }) + }) + + describe('getAccRewardsPerAllocatedToken', function () { + it('accrued per allocated token', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Check + const sg1 = await rewardsManager.subgraphs(subgraphDeploymentID1) + // We trust this function because it was individually tested in previous test + const accRewardsForSubgraphSG1 = await rewardsManager.getAccRewardsForSubgraph(subgraphDeploymentID1) + const accruedRewardsSG1 = accRewardsForSubgraphSG1.sub(sg1.accRewardsForSubgraphSnapshot) + const expectedRewardsAT1 = accruedRewardsSG1.mul(WeiPerEther).div(tokensToAllocate) + const contractRewardsAT1 = (await rewardsManager.getAccRewardsPerAllocatedToken(subgraphDeploymentID1))[0] + expect(expectedRewardsAT1).eq(contractRewardsAT1) + }) + }) + + describe('onSubgraphAllocationUpdate', function () { + it('update the accumulated rewards for allocated tokens state', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Prepare expected results + const expectedSubgraphRewards = toGRT('1400') // 7 blocks since signaling to when we do getAccRewardsForSubgraph + const expectedRewardsAT = toGRT('0.08') // allocated during 5 blocks: 1000 GRT, divided by 12500 allocated tokens + + // Update + await rewardsManager.connect(governor).onSubgraphAllocationUpdate(subgraphDeploymentID1) + + // Check on demand results saved + const subgraph = await rewardsManager.subgraphs(subgraphDeploymentID1) + const contractSubgraphRewards = await rewardsManager.getAccRewardsForSubgraph(subgraphDeploymentID1) + const contractRewardsAT = subgraph.accRewardsPerAllocatedToken + + expect(toRound(expectedSubgraphRewards)).eq(toRound(contractSubgraphRewards)) + expect(toRound(expectedRewardsAT.mul(1000))).eq(toRound(contractRewardsAT.mul(1000))) + }) + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-config.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-config.test.ts new file mode 100644 index 000000000..8edcbb113 --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-config.test.ts @@ -0,0 +1,158 @@ +import { Curation } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { IStaking } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { GraphNetworkContracts, helpers, randomHexBytes, toBN, toGRT } from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +const ISSUANCE_PER_BLOCK = toBN('200000000000000000000') // 200 GRT every block + +describe('Rewards - Configuration', () => { + const graph = hre.graph() + let governor: SignerWithAddress + let indexer1: SignerWithAddress + let indexer2: SignerWithAddress + let curator1: SignerWithAddress + let curator2: SignerWithAddress + let oracle: SignerWithAddress + let assetHolder: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let staking: IStaking + let rewardsManager: RewardsManager + + const subgraphDeploymentID1 = randomHexBytes() + + before(async function () { + const testAccounts = await graph.getTestAccounts() + ;[indexer1, indexer2, curator1, curator2, oracle, assetHolder] = testAccounts + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + staking = contracts.Staking as IStaking + rewardsManager = contracts.RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [indexer1, indexer2, curator1, curator2, assetHolder]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(staking.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + describe('configuration', function () { + describe('initialize', function () { + it('should revert when called on implementation contract', async function () { + // Try to call initialize on the implementation contract (should revert with onlyImpl) + const tx = rewardsManager.connect(governor).initialize(contracts.Controller.address) + await expect(tx).revertedWith('Only implementation') + }) + }) + + describe('issuance per block update', function () { + it('should reject set issuance per block if unauthorized', async function () { + const tx = rewardsManager.connect(indexer1).setIssuancePerBlock(toGRT('1.025')) + await expect(tx).revertedWith('Only Controller governor') + }) + + it('should set issuance rate to minimum allowed (0)', async function () { + const newIssuancePerBlock = toGRT('0') + await rewardsManager.connect(governor).setIssuancePerBlock(newIssuancePerBlock) + expect(await rewardsManager.issuancePerBlock()).eq(newIssuancePerBlock) + }) + + it('should set issuance rate', async function () { + const newIssuancePerBlock = toGRT('100.025') + await rewardsManager.connect(governor).setIssuancePerBlock(newIssuancePerBlock) + expect(await rewardsManager.issuancePerBlock()).eq(newIssuancePerBlock) + expect(await rewardsManager.accRewardsPerSignalLastBlockUpdated()).eq(await helpers.latestBlock()) + }) + }) + + describe('subgraph availability service', function () { + it('should reject set subgraph oracle if unauthorized', async function () { + const tx = rewardsManager.connect(indexer1).setSubgraphAvailabilityOracle(oracle.address) + await expect(tx).revertedWith('Only Controller governor') + }) + + it('should set subgraph oracle if governor', async function () { + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(oracle.address) + expect(await rewardsManager.subgraphAvailabilityOracle()).eq(oracle.address) + }) + + it('should reject to deny subgraph if not the oracle', async function () { + const tx = rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + await expect(tx).revertedWith('Caller must be the subgraph availability oracle') + }) + + it('should deny subgraph', async function () { + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(oracle.address) + + const tx = rewardsManager.connect(oracle).setDenied(subgraphDeploymentID1, true) + const blockNum = await helpers.latestBlock() + await expect(tx) + .emit(rewardsManager, 'RewardsDenylistUpdated') + .withArgs(subgraphDeploymentID1, blockNum + 1) + expect(await rewardsManager.isDenied(subgraphDeploymentID1)).eq(true) + }) + + it('should allow removing subgraph from denylist', async function () { + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(oracle.address) + + // First deny the subgraph + await rewardsManager.connect(oracle).setDenied(subgraphDeploymentID1, true) + expect(await rewardsManager.isDenied(subgraphDeploymentID1)).eq(true) + + // Then remove from denylist + const tx = rewardsManager.connect(oracle).setDenied(subgraphDeploymentID1, false) + await expect(tx).emit(rewardsManager, 'RewardsDenylistUpdated').withArgs(subgraphDeploymentID1, 0) + expect(await rewardsManager.isDenied(subgraphDeploymentID1)).eq(false) + }) + + it('should reject setMinimumSubgraphSignal if unauthorized', async function () { + const tx = rewardsManager.connect(indexer1).setMinimumSubgraphSignal(toGRT('1000')) + await expect(tx).revertedWith('Not authorized') + }) + + it('should allow setMinimumSubgraphSignal from subgraph availability oracle', async function () { + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(oracle.address) + + const newMinimumSignal = toGRT('2000') + const tx = rewardsManager.connect(oracle).setMinimumSubgraphSignal(newMinimumSignal) + await expect(tx).emit(rewardsManager, 'ParameterUpdated').withArgs('minimumSubgraphSignal') + + expect(await rewardsManager.minimumSubgraphSignal()).eq(newMinimumSignal) + }) + + it('should allow setMinimumSubgraphSignal from governor', async function () { + const newMinimumSignal = toGRT('3000') + const tx = rewardsManager.connect(governor).setMinimumSubgraphSignal(newMinimumSignal) + await expect(tx).emit(rewardsManager, 'ParameterUpdated').withArgs('minimumSubgraphSignal') + + expect(await rewardsManager.minimumSubgraphSignal()).eq(newMinimumSignal) + }) + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-distribution.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-distribution.test.ts new file mode 100644 index 000000000..cb3f46107 --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-distribution.test.ts @@ -0,0 +1,708 @@ +import { Curation } from '@graphprotocol/contracts' +import { EpochManager } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { IStaking } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { + deriveChannelKey, + formatGRT, + GraphNetworkContracts, + helpers, + randomHexBytes, + toBN, + toGRT, +} from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { BigNumber, constants } from 'ethers' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +const MAX_PPM = 1000000 + +const { HashZero, WeiPerEther } = constants + +const toRound = (n: BigNumber) => formatGRT(n.add(toGRT('0.5'))).split('.')[0] + +describe('Rewards - Distribution', () => { + const graph = hre.graph() + let delegator: SignerWithAddress + let governor: SignerWithAddress + let curator1: SignerWithAddress + let curator2: SignerWithAddress + let indexer1: SignerWithAddress + let assetHolder: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let epochManager: EpochManager + let staking: IStaking + let rewardsManager: RewardsManager + + // Derive some channel keys for each indexer used to sign attestations + const channelKey1 = deriveChannelKey() + const channelKey2 = deriveChannelKey() + const channelKeyNull = deriveChannelKey() + + const subgraphDeploymentID1 = randomHexBytes() + const subgraphDeploymentID2 = randomHexBytes() + + const allocationID1 = channelKey1.address + const allocationID2 = channelKey2.address + const allocationIDNull = channelKeyNull.address + + const metadata = HashZero + + const ISSUANCE_RATE_PERIODS = 4 // blocks required to issue 800 GRT rewards + const ISSUANCE_PER_BLOCK = toBN('200000000000000000000') // 200 GRT every block + + before(async function () { + ;[delegator, curator1, curator2, indexer1, assetHolder] = await graph.getTestAccounts() + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + epochManager = contracts.EpochManager + staking = contracts.Staking as IStaking + rewardsManager = contracts.RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [indexer1, curator1, curator2, assetHolder]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(staking.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + context('issuing rewards', function () { + beforeEach(async function () { + // 5% minute rate (4 blocks) + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + }) + + describe('getRewards', function () { + it('calculate rewards using the subgraph signalled + allocated tokens', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + + // Rewards + const contractRewards = await rewardsManager.getRewards(staking.address, allocationID1) + + // We trust using this function in the test because we tested it + // standalone in a previous test + const contractRewardsAT1 = (await rewardsManager.getAccRewardsPerAllocatedToken(subgraphDeploymentID1))[0] + + const expectedRewards = contractRewardsAT1.mul(tokensToAllocate).div(WeiPerEther) + expect(expectedRewards).eq(contractRewards) + }) + it('rewards should be zero if the allocation is closed', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Jump + await helpers.mine(ISSUANCE_RATE_PERIODS) + await helpers.mineEpoch(epochManager) + + // Close allocation + await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + + // Rewards + const contractRewards = await rewardsManager.getRewards(staking.address, allocationID1) + expect(contractRewards).eq(BigNumber.from(0)) + }) + it('rewards should be zero if the allocation does not exist', async function () { + // Rewards + const contractRewards = await rewardsManager.getRewards(staking.address, allocationIDNull) + expect(contractRewards).eq(BigNumber.from(0)) + }) + }) + + describe('takeRewards', function () { + interface DelegationParameters { + indexingRewardCut: BigNumber + queryFeeCut: BigNumber + cooldownBlocks: number + } + + async function setupIndexerAllocation() { + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + } + + async function setupIndexerAllocationSignalingAfter() { + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + } + + async function setupIndexerAllocationWithDelegation( + tokensToDelegate: BigNumber, + delegationParams: DelegationParameters, + ) { + const tokensToAllocate = toGRT('12500') + + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Transfer some funds from the curator, I don't want to mint new tokens + await grt.connect(curator1).transfer(delegator.address, tokensToDelegate) + await grt.connect(delegator).approve(staking.address, tokensToDelegate) + + // Stake and set delegation parameters + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .setDelegationParameters(delegationParams.indexingRewardCut, delegationParams.queryFeeCut, 0) + + // Delegate + await staking.connect(delegator).delegate(indexer1.address, tokensToDelegate) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + } + + it('should distribute rewards on closed allocation and stake', async function () { + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup + await setupIndexerAllocation() + + // Jump + await helpers.mineEpoch(epochManager) + + // Before state + const beforeTokenSupply = await grt.totalSupply() + const beforeIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + const beforeIndexer1Balance = await grt.balanceOf(indexer1.address) + const beforeStakingBalance = await grt.balanceOf(staking.address) + + // All the rewards in this subgraph go to this allocation. + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 2 blocks after the signal is minted. + // The final snapshot is when we close the allocation, that happens 9 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 7) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('1400') + + // Close allocation. At this point rewards should be collected for that indexer + const tx = await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + const receipt = await tx.wait() + const event = rewardsManager.interface.parseLog(receipt.logs[1]).args + expect(event.indexer).eq(indexer1.address) + expect(event.allocationID).eq(allocationID1) + expect(toRound(event.amount)).eq(toRound(expectedIndexingRewards)) + + // After state + const afterTokenSupply = await grt.totalSupply() + const afterIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + const afterIndexer1Balance = await grt.balanceOf(indexer1.address) + const afterStakingBalance = await grt.balanceOf(staking.address) + + // Check that rewards are put into indexer stake + const expectedIndexerStake = beforeIndexer1Stake.add(expectedIndexingRewards) + const expectedTokenSupply = beforeTokenSupply.add(expectedIndexingRewards) + // Check stake should have increased with the rewards staked + expect(toRound(afterIndexer1Stake)).eq(toRound(expectedIndexerStake)) + // Check indexer balance remains the same + expect(afterIndexer1Balance).eq(beforeIndexer1Balance) + // Check indexing rewards are kept in the staking contract + expect(toRound(afterStakingBalance)).eq(toRound(beforeStakingBalance.add(expectedIndexingRewards))) + // Check that tokens have been minted + expect(toRound(afterTokenSupply)).eq(toRound(expectedTokenSupply)) + }) + + it('does not revert with an underflow if the minimum signal changes', async function () { + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup + await setupIndexerAllocation() + + await rewardsManager.connect(governor).setMinimumSubgraphSignal(toGRT(14000)) + + // Jump + await helpers.mineEpoch(epochManager) + + // Close allocation. At this point rewards should be collected for that indexer + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, toBN(0)) + }) + + it('does not revert with an underflow if the minimum signal changes, and signal came after allocation', async function () { + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup + await setupIndexerAllocationSignalingAfter() + + await rewardsManager.connect(governor).setMinimumSubgraphSignal(toGRT(14000)) + + // Jump + await helpers.mineEpoch(epochManager) + + // Close allocation. At this point rewards should be collected for that indexer + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, toBN(0)) + }) + + it('does not revert if signal was already under minimum', async function () { + await rewardsManager.connect(governor).setMinimumSubgraphSignal(toGRT(2000)) + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup + await setupIndexerAllocation() + + // Jump + await helpers.mineEpoch(epochManager) + // Close allocation. At this point rewards should be collected for that indexer + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, toBN(0)) + }) + + it('should distribute rewards on closed allocation and send to destination', async function () { + const destinationAddress = randomHexBytes(20) + await staking.connect(indexer1).setRewardsDestination(destinationAddress) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup + await setupIndexerAllocation() + + // Jump + await helpers.mineEpoch(epochManager) + + // Before state + const beforeTokenSupply = await grt.totalSupply() + const beforeIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + const beforeDestinationBalance = await grt.balanceOf(destinationAddress) + const beforeStakingBalance = await grt.balanceOf(staking.address) + + // All the rewards in this subgraph go to this allocation. + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 2 blocks after the signal is minted. + // The final snapshot is when we close the allocation, that happens 9 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 7) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('1400') + + // Close allocation. At this point rewards should be collected for that indexer + const tx = await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + const receipt = await tx.wait() + const event = rewardsManager.interface.parseLog(receipt.logs[1]).args + expect(event.indexer).eq(indexer1.address) + expect(event.allocationID).eq(allocationID1) + expect(toRound(event.amount)).eq(toRound(expectedIndexingRewards)) + + // After state + const afterTokenSupply = await grt.totalSupply() + const afterIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + const afterDestinationBalance = await grt.balanceOf(destinationAddress) + const afterStakingBalance = await grt.balanceOf(staking.address) + + // Check that rewards are properly assigned + const expectedIndexerStake = beforeIndexer1Stake + const expectedTokenSupply = beforeTokenSupply.add(expectedIndexingRewards) + // Check stake should not have changed + expect(toRound(afterIndexer1Stake)).eq(toRound(expectedIndexerStake)) + // Check indexing rewards are received by the rewards destination + expect(toRound(afterDestinationBalance)).eq(toRound(beforeDestinationBalance.add(expectedIndexingRewards))) + // Check indexing rewards were not sent to the staking contract + expect(afterStakingBalance).eq(beforeStakingBalance) + // Check that tokens have been minted + expect(toRound(afterTokenSupply)).eq(toRound(expectedTokenSupply)) + }) + + it('should distribute rewards on closed allocation w/delegators', async function () { + // Setup + const delegationParams = { + indexingRewardCut: toBN('823000'), // 82.30% + queryFeeCut: toBN('80000'), // 8% + cooldownBlocks: 0, + } + const tokensToDelegate = toGRT('2000') + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + // Setup the allocation and delegators + await setupIndexerAllocationWithDelegation(tokensToDelegate, delegationParams) + + // Jump + await helpers.mineEpoch(epochManager) + + // Before state + const beforeTokenSupply = await grt.totalSupply() + const beforeDelegationPool = await staking.delegationPools(indexer1.address) + const beforeIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + + // Close allocation. At this point rewards should be collected for that indexer + await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + + // After state + const afterTokenSupply = await grt.totalSupply() + const afterDelegationPool = await staking.delegationPools(indexer1.address) + const afterIndexer1Stake = await staking.getIndexerStakedTokens(indexer1.address) + + // Check that rewards are put into indexer stake (only indexer cut) + // Check that rewards are put into delegators pool accordingly + + // All the rewards in this subgraph go to this allocation. + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 1 block after the signal is minted. + // The final snapshot is when we close the allocation, that happens 4 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 3) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('600') + // Calculate delegators cut + const indexerRewards = delegationParams.indexingRewardCut.mul(expectedIndexingRewards).div(toBN(MAX_PPM)) + // Calculate indexer cut + const delegatorsRewards = expectedIndexingRewards.sub(indexerRewards) + // Check + const expectedIndexerStake = beforeIndexer1Stake.add(indexerRewards) + const expectedDelegatorsPoolTokens = beforeDelegationPool.tokens.add(delegatorsRewards) + const expectedTokenSupply = beforeTokenSupply.add(expectedIndexingRewards) + expect(toRound(afterIndexer1Stake)).eq(toRound(expectedIndexerStake)) + expect(toRound(afterDelegationPool.tokens)).eq(toRound(expectedDelegatorsPoolTokens)) + // Check that tokens have been minted + expect(toRound(afterTokenSupply)).eq(toRound(expectedTokenSupply)) + }) + + it('should deny rewards if subgraph on denylist', async function () { + // Setup + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + await setupIndexerAllocation() + + // Jump + await helpers.mineEpoch(epochManager) + + // Close allocation. At this point rewards should be collected for that indexer + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx).emit(rewardsManager, 'RewardsDenied').withArgs(indexer1.address, allocationID1) + }) + + it('should handle zero rewards scenario correctly', async function () { + // Setup allocation with zero issuance to create zero rewards scenario + await rewardsManager.connect(governor).setIssuancePerBlock(0) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Before state + const beforeTokenSupply = await grt.totalSupply() + const beforeStakingBalance = await grt.balanceOf(staking.address) + + // Close allocation. At this point rewards should be zero + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx).emit(rewardsManager, 'HorizonRewardsAssigned').withArgs(indexer1.address, allocationID1, 0) + + // After state - should be unchanged since no rewards were minted + const afterTokenSupply = await grt.totalSupply() + const afterStakingBalance = await grt.balanceOf(staking.address) + + // Check that no tokens were minted (rewards were 0) + expect(afterTokenSupply).eq(beforeTokenSupply) + expect(afterStakingBalance).eq(beforeStakingBalance) + }) + }) + }) + + describe('edge scenarios', function () { + it('close allocation on a subgraph that no longer have signal', async function () { + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + + // Jump + await helpers.mineEpoch(epochManager) + + // Remove all signal from the subgraph + const curatorShares = await curation.getCuratorSignal(curator1.address, subgraphDeploymentID1) + await curation.connect(curator1).burn(subgraphDeploymentID1, curatorShares, 0) + + // Close allocation. At this point rewards should be collected for that indexer + await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + }) + }) + + describe('multiple allocations', function () { + it('two allocations in the same block with a GRT burn in the middle should succeed', async function () { + // If rewards are not monotonically increasing, this can trigger + // a subtraction overflow error as seen in mainnet tx: + // 0xb6bf7bbc446720a7409c482d714aebac239dd62e671c3c94f7e93dd3a61835ab + await helpers.mineEpoch(epochManager) + + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Stake + const tokensToStake = toGRT('12500') + await staking.connect(indexer1).stake(tokensToStake) + + // Allocate simultaneously, burning in the middle + const tokensToAlloc = toGRT('5000') + await helpers.setAutoMine(false) + const tx1 = await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAlloc, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + const tx2 = await grt.connect(indexer1).burn(toGRT(1)) + const tx3 = await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAlloc, + allocationID2, + metadata, + await channelKey2.generateProof(indexer1.address), + ) + + await helpers.mine() + await helpers.setAutoMine(true) + + await expect(tx1).emit(staking, 'AllocationCreated') + await expect(tx2).emit(grt, 'Transfer') + await expect(tx3).emit(staking, 'AllocationCreated') + }) + it('two simultanous-similar allocations should get same amount of rewards', async function () { + await helpers.mineEpoch(epochManager) + + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Stake + const tokensToStake = toGRT('12500') + await staking.connect(indexer1).stake(tokensToStake) + + // Allocate simultaneously + const tokensToAlloc = toGRT('5000') + const tx1 = await staking.populateTransaction.allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAlloc, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + const tx2 = await staking.populateTransaction.allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAlloc, + allocationID2, + metadata, + await channelKey2.generateProof(indexer1.address), + ) + await staking.connect(indexer1).multicall([tx1.data, tx2.data]) + + // Jump + await helpers.mineEpoch(epochManager) + + // Close allocations simultaneously + const tx3 = await staking.populateTransaction.closeAllocation(allocationID1, randomHexBytes()) + const tx4 = await staking.populateTransaction.closeAllocation(allocationID2, randomHexBytes()) + const tx5 = await staking.connect(indexer1).multicall([tx3.data, tx4.data]) + + // Both allocations should receive the same amount of rewards + const receipt = await tx5.wait() + const event1 = rewardsManager.interface.parseLog(receipt.logs[1]).args + const event2 = rewardsManager.interface.parseLog(receipt.logs[5]).args + expect(event1.amount).eq(event2.amount) + }) + }) + + describe('rewards progression when collecting query fees', function () { + it('collect query fees with two subgraphs and one allocation', async function () { + async function getRewardsAccrual(subgraphs) { + const [sg1, sg2] = await Promise.all(subgraphs.map((sg) => rewardsManager.getAccRewardsForSubgraph(sg))) + return { + sg1, + sg2, + all: sg1.add(sg2), + } + } + + // set curation percentage + await staking.connect(governor).setCurationPercentage(100000) + + // allow the asset holder + const tokensToCollect = toGRT('10000') + + // signal in two subgraphs in the same block + const subgraphs = [subgraphDeploymentID1, subgraphDeploymentID2] + for (const sub of subgraphs) { + await curation.connect(curator1).mint(sub, toGRT('1500'), 0) + } + + // snapshot block before any accrual (we substract 1 because accrual starts after the first mint happens) + const b1 = await epochManager.blockNum().then((x) => x.toNumber() - 1) + + // allocate + const tokensToAllocate = toGRT('12500') + await staking + .connect(indexer1) + .multicall([ + await staking.populateTransaction.stake(tokensToAllocate).then((tx) => tx.data), + await staking.populateTransaction + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + .then((tx) => tx.data), + ]) + + // move time fwd + await helpers.mineEpoch(epochManager) + + // collect funds into staking for that sub + await staking.connect(assetHolder).collect(tokensToCollect, allocationID1) + + // check rewards diff + await rewardsManager.getRewards(staking.address, allocationID1).then(formatGRT) + + await helpers.mine() + const accrual = await getRewardsAccrual(subgraphs) + const b2 = await epochManager.blockNum().then((x) => x.toNumber()) + + // round comparison because there is a small precision error due to dividing and accrual per signal + expect(toRound(accrual.all)).eq(toRound(ISSUANCE_PER_BLOCK.mul(b2 - b1))) + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-eligibility-oracle.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-eligibility-oracle.test.ts new file mode 100644 index 000000000..108eb3391 --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-eligibility-oracle.test.ts @@ -0,0 +1,496 @@ +import { Curation } from '@graphprotocol/contracts' +import { EpochManager } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { IStaking } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { deriveChannelKey, GraphNetworkContracts, helpers, randomHexBytes, toGRT } from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { constants } from 'ethers' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +const { HashZero } = constants + +describe('Rewards - Eligibility Oracle', () => { + const graph = hre.graph() + let curator1: SignerWithAddress + let governor: SignerWithAddress + let indexer1: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let epochManager: EpochManager + let staking: IStaking + let rewardsManager: RewardsManager + + // Derive channel key for indexer used to sign attestations + const channelKey1 = deriveChannelKey() + + const subgraphDeploymentID1 = randomHexBytes() + + const allocationID1 = channelKey1.address + + const metadata = HashZero + + const ISSUANCE_PER_BLOCK = toGRT('200') // 200 GRT every block + + async function setupIndexerAllocation() { + // Setup + await epochManager.connect(governor).setEpochLength(10) + + // Update total signalled + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Allocate + const tokensToAllocate = toGRT('12500') + await staking.connect(indexer1).stake(tokensToAllocate) + await staking + .connect(indexer1) + .allocateFrom( + indexer1.address, + subgraphDeploymentID1, + tokensToAllocate, + allocationID1, + metadata, + await channelKey1.generateProof(indexer1.address), + ) + } + + before(async function () { + const testAccounts = await graph.getTestAccounts() + curator1 = testAccounts[0] + indexer1 = testAccounts[1] + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + epochManager = contracts.EpochManager + staking = contracts.Staking as IStaking + rewardsManager = contracts.RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [indexer1, curator1]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(staking.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + describe('rewards eligibility oracle', function () { + it('should reject setRewardsEligibilityOracle if unauthorized', async function () { + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) + await mockOracle.deployed() + const tx = rewardsManager.connect(indexer1).setRewardsEligibilityOracle(mockOracle.address) + await expect(tx).revertedWith('Only Controller governor') + }) + + it('should set rewards eligibility oracle if governor', async function () { + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) + await mockOracle.deployed() + + const tx = rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + await expect(tx) + .emit(rewardsManager, 'RewardsEligibilityOracleSet') + .withArgs(constants.AddressZero, mockOracle.address) + + expect(await rewardsManager.rewardsEligibilityOracle()).eq(mockOracle.address) + }) + + it('should allow setting rewards eligibility oracle to zero address', async function () { + // First set an oracle + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Then set to zero address to disable + const tx = rewardsManager.connect(governor).setRewardsEligibilityOracle(constants.AddressZero) + await expect(tx) + .emit(rewardsManager, 'RewardsEligibilityOracleSet') + .withArgs(mockOracle.address, constants.AddressZero) + + expect(await rewardsManager.rewardsEligibilityOracle()).eq(constants.AddressZero) + }) + + it('should reject setting oracle that does not support interface', async function () { + // Try to set an EOA (externally owned account) as the rewards eligibility oracle + const tx = rewardsManager.connect(governor).setRewardsEligibilityOracle(indexer1.address) + // EOA doesn't have code, so the call will revert (error message may vary by ethers version) + await expect(tx).to.be.reverted + }) + + it('should reject setting oracle that does not support IRewardsEligibility interface', async function () { + // Deploy a contract that supports ERC165 but not IRewardsEligibility + const MockERC165Factory = await hre.ethers.getContractFactory('contracts/tests/MockERC165.sol:MockERC165') + const mockERC165 = await MockERC165Factory.deploy() + await mockERC165.deployed() + + const tx = rewardsManager.connect(governor).setRewardsEligibilityOracle(mockERC165.address) + await expect(tx).revertedWith('Contract does not support IRewardsEligibility interface') + }) + + it('should not emit event when setting same oracle address', async function () { + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Setting the same oracle again should not emit an event + const tx = rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + await expect(tx).to.not.emit(rewardsManager, 'RewardsEligibilityOracleSet') + }) + }) + + describe('rewards eligibility in takeRewards', function () { + it('should deny rewards due to rewards eligibility oracle', async function () { + // Setup rewards eligibility oracle that denies rewards for indexer1 + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(false) // Default to deny + await mockOracle.deployed() + + // Set the rewards eligibility oracle + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Calculate expected rewards (for verification in the event) + const expectedIndexingRewards = toGRT('1400') + + // Close allocation. At this point rewards should be denied due to eligibility + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'RewardsDeniedDueToEligibility') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should allow rewards when rewards eligibility oracle approves', async function () { + // Setup rewards eligibility oracle that allows rewards for indexer1 + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) // Default to allow + await mockOracle.deployed() + + // Set the rewards eligibility oracle + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Calculate expected rewards + const expectedIndexingRewards = toGRT('1400') + + // Close allocation. At this point rewards should be assigned normally + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + }) + + describe('rewards eligibility oracle and denylist interaction', function () { + it('should prioritize denylist over REO when both deny', async function () { + // Setup BOTH denial mechanisms + // 1. Setup denylist + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + + // 2. Setup REO that also denies + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(false) // Deny + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Close allocation - denylist should be checked first + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + + // Verify: Denylist wins (checked first in RewardsManager.takeRewards line 522) + // Should emit RewardsDenied (not RewardsDeniedDueToEligibility) + await expect(tx).emit(rewardsManager, 'RewardsDenied').withArgs(indexer1.address, allocationID1) + + // Verify: REO event is NOT emitted + await expect(tx).to.not.emit(rewardsManager, 'RewardsDeniedDueToEligibility') + }) + + it('should check REO when denylist allows but indexer ineligible', async function () { + // Setup: Subgraph is allowed (no denylist), but indexer is ineligible + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(false) // Deny indexer + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + const expectedIndexingRewards = toGRT('1400') + + // Close allocation - REO should be checked + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'RewardsDeniedDueToEligibility') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should handle indexer becoming ineligible mid-allocation', async function () { + // Setup: Indexer starts eligible + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(true) // Start eligible + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation while indexer is eligible + await setupIndexerAllocation() + + // Jump to next epoch (rewards accrue) + await helpers.mineEpoch(epochManager) + + // Change eligibility AFTER allocation created but BEFORE closing + await mockOracle.setIndexerEligible(indexer1.address, false) + + const expectedIndexingRewards = toGRT('1600') + + // Close allocation - should be denied at close time (not creation time) + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'RewardsDeniedDueToEligibility') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should handle indexer becoming eligible mid-allocation', async function () { + // Setup: Indexer starts ineligible + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(false) // Start ineligible + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation while indexer is ineligible + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Change eligibility before closing + await mockOracle.setIndexerEligible(indexer1.address, true) + + const expectedIndexingRewards = toGRT('1600') + + // Close allocation - should now be allowed + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should handle denylist being added mid-allocation', async function () { + // Setup: Start with subgraph NOT denied + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation when subgraph is allowed + await setupIndexerAllocation() + + // Jump to next epoch (rewards accrue) + await helpers.mineEpoch(epochManager) + + // Deny the subgraph before closing allocation + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + + // Close allocation - should be denied even though it was created when allowed + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx).emit(rewardsManager, 'RewardsDenied').withArgs(indexer1.address, allocationID1) + }) + + it('should handle denylist being removed mid-allocation', async function () { + // Setup: Start with subgraph denied + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation (can still allocate to denied subgraph) + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + // Remove from denylist before closing + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, false) + + const expectedIndexingRewards = toGRT('1600') + + // Close allocation - should now get rewards + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should allow rewards when REO is zero address (disabled)', async function () { + // Ensure REO is not set (zero address = disabled) + expect(await rewardsManager.rewardsEligibilityOracle()).eq(constants.AddressZero) + + // Align with the epoch boundary + await helpers.mineEpoch(epochManager) + + // Setup allocation + await setupIndexerAllocation() + + // Jump to next epoch + await helpers.mineEpoch(epochManager) + + const expectedIndexingRewards = toGRT('1400') + + // Close allocation - should get rewards (no eligibility check when REO is zero) + const tx = staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + await expect(tx) + .emit(rewardsManager, 'HorizonRewardsAssigned') + .withArgs(indexer1.address, allocationID1, expectedIndexingRewards) + }) + + it('should verify event structure differences between denial mechanisms', async function () { + // Test 1: Denylist denial - event WITHOUT amount + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + + await helpers.mineEpoch(epochManager) + await setupIndexerAllocation() + await helpers.mineEpoch(epochManager) + + const tx1 = await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + const receipt1 = await tx1.wait() + + // Find the RewardsDenied event - search in logs as events may be from different contracts + const rewardsDeniedEvent = receipt1.logs + .map((log) => { + try { + return rewardsManager.interface.parseLog(log) + } catch { + return null + } + }) + .find((event) => event?.name === 'RewardsDenied') + + expect(rewardsDeniedEvent).to.not.be.undefined + + // Verify it only has indexer and allocationID (no amount parameter) + expect(rewardsDeniedEvent?.args?.indexer).to.equal(indexer1.address) + expect(rewardsDeniedEvent?.args?.allocationID).to.equal(allocationID1) + // RewardsDenied has only 2 args, amount should not exist + expect(rewardsDeniedEvent?.args?.amount).to.be.undefined + + // Reset for test 2 + await fixture.tearDown() + await fixture.setUp() + + // Test 2: REO denial - event WITH amount + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockOracle = await MockRewardsEligibilityOracleFactory.deploy(false) + await mockOracle.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockOracle.address) + + await helpers.mineEpoch(epochManager) + await setupIndexerAllocation() + await helpers.mineEpoch(epochManager) + + const tx2 = await staking.connect(indexer1).closeAllocation(allocationID1, randomHexBytes()) + const receipt2 = await tx2.wait() + + // Find the RewardsDeniedDueToEligibility event + const eligibilityEvent = receipt2.logs + .map((log) => { + try { + return rewardsManager.interface.parseLog(log) + } catch { + return null + } + }) + .find((event) => event?.name === 'RewardsDeniedDueToEligibility') + + expect(eligibilityEvent).to.not.be.undefined + + // Verify it has indexer, allocationID, AND amount + expect(eligibilityEvent?.args?.indexer).to.equal(indexer1.address) + expect(eligibilityEvent?.args?.allocationID).to.equal(allocationID1) + expect(eligibilityEvent?.args?.amount).to.not.be.undefined + expect(eligibilityEvent?.args?.amount).to.be.gt(0) // Shows what they would have gotten + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-interface.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-interface.test.ts new file mode 100644 index 000000000..3a9b7c23b --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-interface.test.ts @@ -0,0 +1,116 @@ +import { RewardsManager } from '@graphprotocol/contracts' +import { IERC165__factory, IIssuanceTarget__factory, IRewardsManager__factory } from '@graphprotocol/interfaces/types' +import { GraphNetworkContracts, toGRT } from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +describe('RewardsManager interfaces', () => { + const graph = hre.graph() + let governor: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let rewardsManager: RewardsManager + + before(async function () { + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + rewardsManager = contracts.RewardsManager + + // Set a default issuance per block + await rewardsManager.connect(governor).setIssuancePerBlock(toGRT('200')) + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + /** + * Interface ID Stability Tests + * + * These tests verify that interface IDs remain stable across builds. + * Changes to these IDs indicate breaking changes to the interface definitions. + * + * If a test fails: + * 1. Verify the interface change was intentional + * 2. Understand the impact on deployed contracts + * 3. Update the expected ID if the change is correct + * 4. Document the breaking change in release notes + */ + describe('Interface ID Stability', () => { + it('IERC165 should have stable interface ID', () => { + expect(IERC165__factory.interfaceId).to.equal('0x01ffc9a7') + }) + + it('IIssuanceTarget should have stable interface ID', () => { + expect(IIssuanceTarget__factory.interfaceId).to.equal('0xaee4dc43') + }) + + it('IRewardsManager should have stable interface ID', () => { + expect(IRewardsManager__factory.interfaceId).to.equal('0xa31d8306') + }) + }) + + describe('supportsInterface', function () { + it('should support IIssuanceTarget interface', async function () { + const supports = await rewardsManager.supportsInterface(IIssuanceTarget__factory.interfaceId) + expect(supports).to.be.true + }) + + it('should support IRewardsManager interface', async function () { + const supports = await rewardsManager.supportsInterface(IRewardsManager__factory.interfaceId) + expect(supports).to.be.true + }) + + it('should support IERC165 interface', async function () { + const supports = await rewardsManager.supportsInterface(IERC165__factory.interfaceId) + expect(supports).to.be.true + }) + + it('should return false for unsupported interfaces', async function () { + // Test with an unknown interface ID + const unknownInterfaceId = '0x12345678' // Random interface ID + const supports = await rewardsManager.supportsInterface(unknownInterfaceId) + expect(supports).to.be.false + }) + }) + + describe('calcRewards', function () { + it('should calculate rewards correctly', async function () { + const tokens = toGRT('1000') + const accRewardsPerAllocatedToken = toGRT('0.5') + + // Expected: (1000 * 0.5 * 1e18) / 1e18 = 500 GRT + const expectedRewards = toGRT('500') + + const rewards = await rewardsManager.calcRewards(tokens, accRewardsPerAllocatedToken) + expect(rewards).to.equal(expectedRewards) + }) + + it('should return 0 when tokens is 0', async function () { + const tokens = toGRT('0') + const accRewardsPerAllocatedToken = toGRT('0.5') + + const rewards = await rewardsManager.calcRewards(tokens, accRewardsPerAllocatedToken) + expect(rewards).to.equal(0) + }) + + it('should return 0 when accRewardsPerAllocatedToken is 0', async function () { + const tokens = toGRT('1000') + const accRewardsPerAllocatedToken = toGRT('0') + + const rewards = await rewardsManager.calcRewards(tokens, accRewardsPerAllocatedToken) + expect(rewards).to.equal(0) + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-issuance-allocator.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-issuance-allocator.test.ts new file mode 100644 index 000000000..c74679ad9 --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-issuance-allocator.test.ts @@ -0,0 +1,416 @@ +import { Curation } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { GraphNetworkContracts, helpers, randomHexBytes, toGRT } from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { constants } from 'ethers' +import hre from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +describe('Rewards - Issuance Allocator', () => { + const graph = hre.graph() + let curator1: SignerWithAddress + let governor: SignerWithAddress + let indexer1: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let rewardsManager: RewardsManager + + const subgraphDeploymentID1 = randomHexBytes() + + const ISSUANCE_PER_BLOCK = toGRT('200') // 200 GRT every block + + before(async function () { + const testAccounts = await graph.getTestAccounts() + curator1 = testAccounts[0] + indexer1 = testAccounts[1] + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + rewardsManager = contracts.RewardsManager as RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [curator1]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + // Reset issuance allocator to ensure we use direct issuancePerBlock + await rewardsManager.connect(governor).setIssuanceAllocator(constants.AddressZero) + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + describe('setIssuanceAllocator', function () { + describe('ERC-165 validation', function () { + it('should successfully set an issuance allocator that supports the interface', async function () { + // Deploy a mock issuance allocator that supports ERC-165 and IIssuanceAllocationDistribution + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockAllocator.deployed() + + // Should succeed because MockIssuanceAllocator supports IIssuanceAllocationDistribution + await expect(rewardsManager.connect(governor).setIssuanceAllocator(mockAllocator.address)) + .to.emit(rewardsManager, 'IssuanceAllocatorSet') + .withArgs(constants.AddressZero, mockAllocator.address) + + // Verify the allocator was set + expect(await rewardsManager.issuanceAllocator()).to.equal(mockAllocator.address) + }) + + it('should revert when setting to EOA address (no contract code)', async function () { + const eoaAddress = indexer1.address + + // Should revert because EOAs don't have contract code to call supportsInterface on + await expect(rewardsManager.connect(governor).setIssuanceAllocator(eoaAddress)).to.be.reverted + }) + + it('should revert when setting to contract that does not support IIssuanceAllocationDistribution', async function () { + // Deploy a contract that supports ERC-165 but not IIssuanceAllocationDistribution + const MockERC165Factory = await hre.ethers.getContractFactory('contracts/tests/MockERC165.sol:MockERC165') + const mockERC165 = await MockERC165Factory.deploy() + await mockERC165.deployed() + + // Should revert because the contract doesn't support IIssuanceAllocationDistribution + await expect(rewardsManager.connect(governor).setIssuanceAllocator(mockERC165.address)).to.be.revertedWith( + 'Contract does not support IIssuanceAllocationDistribution interface', + ) + }) + + it('should validate interface before updating rewards calculation', async function () { + // This test ensures that ERC165 validation happens before updateAccRewardsPerSignal + // Deploy a contract that supports ERC-165 but not IIssuanceAllocationDistribution + const MockERC165Factory = await hre.ethers.getContractFactory('contracts/tests/MockERC165.sol:MockERC165') + const mockERC165 = await MockERC165Factory.deploy() + await mockERC165.deployed() + + // Should revert with interface error, not with any rewards calculation error + await expect(rewardsManager.connect(governor).setIssuanceAllocator(mockERC165.address)).to.be.revertedWith( + 'Contract does not support IIssuanceAllocationDistribution interface', + ) + }) + }) + + describe('access control', function () { + it('should revert when called by non-governor', async function () { + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockAllocator.deployed() + + // Should revert because indexer1 is not the governor + await expect(rewardsManager.connect(indexer1).setIssuanceAllocator(mockAllocator.address)).to.be.revertedWith( + 'Only Controller governor', + ) + }) + }) + + describe('state management', function () { + it('should allow setting issuance allocator to zero address (disable)', async function () { + // First set a valid allocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockAllocator.deployed() + + await rewardsManager.connect(governor).setIssuanceAllocator(mockAllocator.address) + expect(await rewardsManager.issuanceAllocator()).to.equal(mockAllocator.address) + + // Now disable by setting to zero address + await expect(rewardsManager.connect(governor).setIssuanceAllocator(constants.AddressZero)) + .to.emit(rewardsManager, 'IssuanceAllocatorSet') + .withArgs(mockAllocator.address, constants.AddressZero) + + expect(await rewardsManager.issuanceAllocator()).to.equal(constants.AddressZero) + + // Should now use local issuancePerBlock again + expect(await rewardsManager.getRewardsIssuancePerBlock()).eq(ISSUANCE_PER_BLOCK) + }) + + it('should emit IssuanceAllocatorSet event when setting allocator', async function () { + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + + const tx = rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + await expect(tx) + .emit(rewardsManager, 'IssuanceAllocatorSet') + .withArgs(constants.AddressZero, mockIssuanceAllocator.address) + }) + + it('should not emit event when setting to same allocator address', async function () { + // Deploy a mock issuance allocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockAllocator.deployed() + + // Set the allocator first time + await rewardsManager.connect(governor).setIssuanceAllocator(mockAllocator.address) + + // Setting to same address should not emit event + const tx = await rewardsManager.connect(governor).setIssuanceAllocator(mockAllocator.address) + const receipt = await tx.wait() + + // Filter for IssuanceAllocatorSet events + const events = receipt.events?.filter((e) => e.event === 'IssuanceAllocatorSet') || [] + expect(events.length).to.equal(0) + }) + + it('should update rewards before changing issuance allocator', async function () { + // This test verifies that updateAccRewardsPerSignal is called when setting allocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + + // Setting the allocator should trigger updateAccRewardsPerSignal + // We can't easily test this directly, but we can verify the allocator was set + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + expect(await rewardsManager.issuanceAllocator()).eq(mockIssuanceAllocator.address) + }) + }) + }) + + describe('getRewardsIssuancePerBlock', function () { + it('should return issuancePerBlock when no issuanceAllocator is set', async function () { + const expectedIssuance = toGRT('100.025') + await rewardsManager.connect(governor).setIssuancePerBlock(expectedIssuance) + + // Ensure no issuanceAllocator is set + expect(await rewardsManager.issuanceAllocator()).eq(constants.AddressZero) + + // Should return the direct issuancePerBlock value + expect(await rewardsManager.getRewardsIssuancePerBlock()).eq(expectedIssuance) + }) + + it('should return value from issuanceAllocator when set', async function () { + // Create a mock IssuanceAllocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + + // Set the mock allocator on RewardsManager + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + // Verify the allocator was set + expect(await rewardsManager.issuanceAllocator()).eq(mockIssuanceAllocator.address) + + // Set RewardsManager as a self-minting target with 25 GRT per block + const expectedIssuance = toGRT('25') + await mockIssuanceAllocator['setTargetAllocation(address,uint256,uint256,bool)']( + rewardsManager.address, + 0, // allocator issuance + expectedIssuance, // self issuance + true, + ) + + // Should return the value from the allocator, not the local issuancePerBlock + expect(await rewardsManager.getRewardsIssuancePerBlock()).eq(expectedIssuance) + }) + + it('should return 0 when issuanceAllocator is set but target not registered as self-minter', async function () { + // Create a mock IssuanceAllocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + + // Set the mock allocator on RewardsManager + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + // Set RewardsManager as an allocator-minting target (only allocator issuance) + await mockIssuanceAllocator['setTargetAllocation(address,uint256,uint256,bool)']( + rewardsManager.address, + toGRT('25'), // allocator issuance + 0, // self issuance + false, + ) + + // Should return 0 because it's not a self-minting target + expect(await rewardsManager.getRewardsIssuancePerBlock()).eq(0) + }) + }) + + describe('setIssuancePerBlock', function () { + it('should allow setIssuancePerBlock when issuanceAllocator is set', async function () { + // Create and set a mock IssuanceAllocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + // Should allow setting issuancePerBlock even when allocator is set + const newIssuancePerBlock = toGRT('100') + await rewardsManager.connect(governor).setIssuancePerBlock(newIssuancePerBlock) + + // The local issuancePerBlock should be updated + expect(await rewardsManager.issuancePerBlock()).eq(newIssuancePerBlock) + + // But the effective issuance should still come from the allocator + // (assuming the allocator returns a different value) + expect(await rewardsManager.getRewardsIssuancePerBlock()).not.eq(newIssuancePerBlock) + }) + }) + + describe('beforeIssuanceAllocationChange', function () { + it('should handle beforeIssuanceAllocationChange correctly', async function () { + // Create and set a mock IssuanceAllocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + const mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + // Anyone should be able to call this function + await rewardsManager.connect(governor).beforeIssuanceAllocationChange() + + // Should also succeed when called by the allocator + await mockIssuanceAllocator.callBeforeIssuanceAllocationChange(rewardsManager.address) + }) + }) + + describe('issuance allocator integration', function () { + let mockIssuanceAllocator: any + + beforeEach(async function () { + // Create and setup mock allocator + const MockIssuanceAllocatorFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockIssuanceAllocator.sol:MockIssuanceAllocator', + ) + mockIssuanceAllocator = await MockIssuanceAllocatorFactory.deploy() + await mockIssuanceAllocator.deployed() + }) + + it('should accumulate rewards using allocator rate over time', async function () { + // Setup: Create signal + const totalSignal = toGRT('1000') + await curation.connect(curator1).mint(subgraphDeploymentID1, totalSignal, 0) + + // Set allocator with specific rate (50 GRT per block, different from local 200 GRT) + const allocatorRate = toGRT('50') + await mockIssuanceAllocator.setTargetAllocation(rewardsManager.address, 0, allocatorRate, false) + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + // Snapshot state after setting allocator + const rewardsAfterSet = await rewardsManager.getAccRewardsPerSignal() + + // Mine blocks to accrue rewards at allocator rate + const blocksToMine = 10 + await helpers.mine(blocksToMine) + + // Get accumulated rewards + const rewardsAfterMining = await rewardsManager.getAccRewardsPerSignal() + const actualAccrued = rewardsAfterMining.sub(rewardsAfterSet) + + // Calculate expected rewards: (rate × blocks) / totalSignal + // Expected = (50 GRT × 10 blocks) / 1000 GRT signal = 0.5 GRT per signal + const expectedAccrued = allocatorRate.mul(blocksToMine).mul(toGRT('1')).div(totalSignal) + + // Verify rewards accumulated at allocator rate (not local rate of 200 GRT/block) + expect(actualAccrued).to.eq(expectedAccrued) + + // Verify NOT using local rate (would be 4x higher: 200 vs 50) + const wrongExpected = ISSUANCE_PER_BLOCK.mul(blocksToMine).mul(toGRT('1')).div(totalSignal) + expect(actualAccrued).to.not.eq(wrongExpected) + }) + + it('should maintain reward consistency when switching between rates', async function () { + // Setup: Create signal + const totalSignal = toGRT('2000') + await curation.connect(curator1).mint(subgraphDeploymentID1, totalSignal, 0) + + // Snapshot initial state + const block0 = await helpers.latestBlock() + const rewards0 = await rewardsManager.getAccRewardsPerSignal() + + // Phase 1: Accrue at local rate (200 GRT/block) + await helpers.mine(5) + const block1 = await helpers.latestBlock() + const rewards1 = await rewardsManager.getAccRewardsPerSignal() + + // Calculate phase 1 accrual + const blocksPhase1 = block1 - block0 + const phase1Accrued = rewards1.sub(rewards0) + const expectedPhase1 = ISSUANCE_PER_BLOCK.mul(blocksPhase1).mul(toGRT('1')).div(totalSignal) + expect(phase1Accrued).to.eq(expectedPhase1) + + // Phase 2: Switch to allocator with different rate (100 GRT/block) + const allocatorRate = toGRT('100') + await mockIssuanceAllocator.setTargetAllocation(rewardsManager.address, 0, allocatorRate, false) + await rewardsManager.connect(governor).setIssuanceAllocator(mockIssuanceAllocator.address) + + const block2 = await helpers.latestBlock() + const rewards2 = await rewardsManager.getAccRewardsPerSignal() + + await helpers.mine(8) + const block3 = await helpers.latestBlock() + const rewards3 = await rewardsManager.getAccRewardsPerSignal() + + // Calculate phase 2 accrual (includes the setIssuanceAllocator block at local rate) + const blocksPhase2 = block3 - block2 + const phase2Accrued = rewards3.sub(rewards2) + const expectedPhase2 = allocatorRate.mul(blocksPhase2).mul(toGRT('1')).div(totalSignal) + expect(phase2Accrued).to.eq(expectedPhase2) + + // Phase 3: Switch back to local rate (200 GRT/block) + await rewardsManager.connect(governor).setIssuanceAllocator(constants.AddressZero) + + const block4 = await helpers.latestBlock() + const rewards4 = await rewardsManager.getAccRewardsPerSignal() + + await helpers.mine(4) + const block5 = await helpers.latestBlock() + const rewards5 = await rewardsManager.getAccRewardsPerSignal() + + // Calculate phase 3 accrual + const blocksPhase3 = block5 - block4 + const phase3Accrued = rewards5.sub(rewards4) + const expectedPhase3 = ISSUANCE_PER_BLOCK.mul(blocksPhase3).mul(toGRT('1')).div(totalSignal) + expect(phase3Accrued).to.eq(expectedPhase3) + + // Verify total consistency: all rewards from start to end must equal sum of all phases + // including the transition blocks (setIssuanceAllocator calls mine blocks too) + const transitionPhase1to2 = rewards2.sub(rewards1) // Block mined by setIssuanceAllocator + const transitionPhase2to3 = rewards4.sub(rewards3) // Block mined by removing allocator + const totalExpected = phase1Accrued + .add(transitionPhase1to2) + .add(phase2Accrued) + .add(transitionPhase2to3) + .add(phase3Accrued) + const totalActual = rewards5.sub(rewards0) + expect(totalActual).to.eq(totalExpected) + }) + }) +}) diff --git a/packages/contracts/test/tests/unit/rewards/rewards-subgraph-service.test.ts b/packages/contracts/test/tests/unit/rewards/rewards-subgraph-service.test.ts new file mode 100644 index 000000000..f75785ecd --- /dev/null +++ b/packages/contracts/test/tests/unit/rewards/rewards-subgraph-service.test.ts @@ -0,0 +1,468 @@ +import { Curation } from '@graphprotocol/contracts' +import { GraphToken } from '@graphprotocol/contracts' +import { RewardsManager } from '@graphprotocol/contracts' +import { GraphNetworkContracts, helpers, randomAddress, randomHexBytes, toGRT } from '@graphprotocol/sdk' +import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { constants } from 'ethers' +import hre from 'hardhat' +import { network } from 'hardhat' + +import { NetworkFixture } from '../lib/fixtures' + +describe('Rewards - SubgraphService', () => { + const graph = hre.graph() + let curator1: SignerWithAddress + let governor: SignerWithAddress + let indexer1: SignerWithAddress + + let fixture: NetworkFixture + + let contracts: GraphNetworkContracts + let grt: GraphToken + let curation: Curation + let rewardsManager: RewardsManager + + const subgraphDeploymentID1 = randomHexBytes() + const allocationID1 = randomAddress() + + const ISSUANCE_PER_BLOCK = toGRT('200') // 200 GRT every block + + before(async function () { + const testAccounts = await graph.getTestAccounts() + curator1 = testAccounts[0] + indexer1 = testAccounts[1] + ;({ governor } = await graph.getNamedAccounts()) + + fixture = new NetworkFixture(graph.provider) + contracts = await fixture.load(governor) + grt = contracts.GraphToken as GraphToken + curation = contracts.Curation as Curation + rewardsManager = contracts.RewardsManager + + // 200 GRT per block + await rewardsManager.connect(governor).setIssuancePerBlock(ISSUANCE_PER_BLOCK) + + // Distribute test funds + for (const wallet of [indexer1, curator1]) { + await grt.connect(governor).mint(wallet.address, toGRT('1000000')) + await grt.connect(wallet).approve(curation.address, toGRT('1000000')) + } + }) + + beforeEach(async function () { + await fixture.setUp() + }) + + afterEach(async function () { + await fixture.tearDown() + }) + + describe('subgraph service configuration', function () { + it('should reject setSubgraphService if unauthorized', async function () { + const newService = randomAddress() + const tx = rewardsManager.connect(indexer1).setSubgraphService(newService) + await expect(tx).revertedWith('Only Controller governor') + }) + + it('should set subgraph service if governor', async function () { + const newService = randomAddress() + const tx = rewardsManager.connect(governor).setSubgraphService(newService) + + await expect(tx).emit(rewardsManager, 'SubgraphServiceSet').withArgs(constants.AddressZero, newService) + + expect(await rewardsManager.subgraphService()).eq(newService) + }) + + it('should allow setting to zero address', async function () { + const service = randomAddress() + await rewardsManager.connect(governor).setSubgraphService(service) + + const tx = rewardsManager.connect(governor).setSubgraphService(constants.AddressZero) + await expect(tx).emit(rewardsManager, 'SubgraphServiceSet').withArgs(service, constants.AddressZero) + + expect(await rewardsManager.subgraphService()).eq(constants.AddressZero) + }) + + it('should emit event when setting different address', async function () { + const service1 = randomAddress() + const service2 = randomAddress() + + await rewardsManager.connect(governor).setSubgraphService(service1) + + // Setting a different address should emit event + const tx = await rewardsManager.connect(governor).setSubgraphService(service2) + await expect(tx).emit(rewardsManager, 'SubgraphServiceSet').withArgs(service1, service2) + }) + }) + + describe('subgraph service as rewards issuer', function () { + let mockSubgraphService: any + + beforeEach(async function () { + // Deploy mock SubgraphService + const MockSubgraphServiceFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockSubgraphService.sol:MockSubgraphService', + ) + mockSubgraphService = await MockSubgraphServiceFactory.deploy() + await mockSubgraphService.deployed() + + // Set it on RewardsManager + await rewardsManager.connect(governor).setSubgraphService(mockSubgraphService.address) + }) + + describe('getRewards from subgraph service', function () { + it('should calculate rewards for subgraph service allocations', async function () { + // Setup: Create signal for rewards calculation + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Setup allocation data in mock + const tokensAllocated = toGRT('12500') + await mockSubgraphService.setAllocation( + allocationID1, + true, // isActive + indexer1.address, + subgraphDeploymentID1, + tokensAllocated, + 0, // accRewardsPerAllocatedToken + 0, // accRewardsPending + ) + + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, tokensAllocated) + + // Mine some blocks to accrue rewards + await helpers.mine(10) + + // Get rewards - should return calculated amount + const rewards = await rewardsManager.getRewards(mockSubgraphService.address, allocationID1) + expect(rewards).to.be.gt(0) + }) + + it('should return zero for inactive allocation', async function () { + // Setup allocation as inactive + await mockSubgraphService.setAllocation( + allocationID1, + false, // isActive = false + indexer1.address, + subgraphDeploymentID1, + toGRT('12500'), + 0, + 0, + ) + + const rewards = await rewardsManager.getRewards(mockSubgraphService.address, allocationID1) + expect(rewards).to.equal(0) + }) + + it('should reject getRewards from non-rewards-issuer contract', async function () { + const randomContract = randomAddress() + const tx = rewardsManager.getRewards(randomContract, allocationID1) + await expect(tx).revertedWith('Not a rewards issuer') + }) + }) + + describe('takeRewards from subgraph service', function () { + it('should take rewards through subgraph service', async function () { + // Setup: Create signal for rewards calculation + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Setup allocation data in mock + const tokensAllocated = toGRT('12500') + await mockSubgraphService.setAllocation( + allocationID1, + true, // isActive + indexer1.address, + subgraphDeploymentID1, + tokensAllocated, + 0, // accRewardsPerAllocatedToken + 0, // accRewardsPending + ) + + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, tokensAllocated) + + // Mine some blocks to accrue rewards + await helpers.mine(10) + + // Before state + const beforeSubgraphServiceBalance = await grt.balanceOf(mockSubgraphService.address) + const beforeTotalSupply = await grt.totalSupply() + + // Impersonate the mock subgraph service contract + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [mockSubgraphService.address], + }) + await network.provider.send('hardhat_setBalance', [mockSubgraphService.address, '0x1000000000000000000']) + + const mockSubgraphServiceSigner = await hre.ethers.getSigner(mockSubgraphService.address) + + // Take rewards (called by subgraph service) + const tx = await rewardsManager.connect(mockSubgraphServiceSigner).takeRewards(allocationID1) + const receipt = await tx.wait() + + // Stop impersonating + await network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [mockSubgraphService.address], + }) + + // Parse the event + const event = receipt.logs + .map((log: any) => { + try { + return rewardsManager.interface.parseLog(log) + } catch { + return null + } + }) + .find((e: any) => e?.name === 'HorizonRewardsAssigned') + + expect(event).to.not.be.undefined + expect(event?.args.indexer).to.equal(indexer1.address) + expect(event?.args.allocationID).to.equal(allocationID1) + expect(event?.args.amount).to.be.gt(0) + + // After state - verify tokens minted to subgraph service + const afterSubgraphServiceBalance = await grt.balanceOf(mockSubgraphService.address) + const afterTotalSupply = await grt.totalSupply() + + expect(afterSubgraphServiceBalance).to.be.gt(beforeSubgraphServiceBalance) + expect(afterTotalSupply).to.be.gt(beforeTotalSupply) + }) + + it('should return zero rewards for inactive allocation', async function () { + // Setup allocation as inactive + await mockSubgraphService.setAllocation( + allocationID1, + false, // isActive = false + indexer1.address, + subgraphDeploymentID1, + toGRT('12500'), + 0, + 0, + ) + + // Impersonate the mock subgraph service contract + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [mockSubgraphService.address], + }) + await network.provider.send('hardhat_setBalance', [mockSubgraphService.address, '0x1000000000000000000']) + + const mockSubgraphServiceSigner = await hre.ethers.getSigner(mockSubgraphService.address) + + // Take rewards should return 0 and emit event with 0 amount + const tx = rewardsManager.connect(mockSubgraphServiceSigner).takeRewards(allocationID1) + await expect(tx).emit(rewardsManager, 'HorizonRewardsAssigned').withArgs(indexer1.address, allocationID1, 0) + + // Stop impersonating + await network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [mockSubgraphService.address], + }) + }) + + it('should reject takeRewards from non-rewards-issuer contract', async function () { + const tx = rewardsManager.connect(indexer1).takeRewards(allocationID1) + await expect(tx).revertedWith('Caller must be a rewards issuer') + }) + + it('should handle zero rewards scenario', async function () { + // Setup with zero issuance + await rewardsManager.connect(governor).setIssuancePerBlock(0) + + // Setup allocation + await mockSubgraphService.setAllocation( + allocationID1, + true, + indexer1.address, + subgraphDeploymentID1, + toGRT('12500'), + 0, + 0, + ) + + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, toGRT('12500')) + + // Mine blocks + await helpers.mine(10) + + // Impersonate the mock subgraph service contract + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [mockSubgraphService.address], + }) + await network.provider.send('hardhat_setBalance', [mockSubgraphService.address, '0x1000000000000000000']) + + const mockSubgraphServiceSigner = await hre.ethers.getSigner(mockSubgraphService.address) + + // Take rewards should succeed with 0 amount + const tx = rewardsManager.connect(mockSubgraphServiceSigner).takeRewards(allocationID1) + await expect(tx).emit(rewardsManager, 'HorizonRewardsAssigned').withArgs(indexer1.address, allocationID1, 0) + + // Stop impersonating + await network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [mockSubgraphService.address], + }) + }) + }) + + describe('mixed allocations from staking and subgraph service', function () { + it('should account for both staking and subgraph service allocations in getAccRewardsPerAllocatedToken', async function () { + // This test verifies that getSubgraphAllocatedTokens is called for both issuers + // and rewards are distributed proportionally + + // Setup: Create signal + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Setup subgraph service allocation + const tokensFromSubgraphService = toGRT('5000') + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, tokensFromSubgraphService) + + // Note: We can't easily create a real staking allocation in this test + // but the contract code at lines 381-388 loops through both issuers + // and sums their allocated tokens. This test verifies the subgraph service path. + + // Mine some blocks + await helpers.mine(5) + + // Get accumulated rewards per allocated token + const [accRewardsPerAllocatedToken, accRewardsForSubgraph] = + await rewardsManager.getAccRewardsPerAllocatedToken(subgraphDeploymentID1) + + // Should have calculated rewards based on subgraph service allocations + expect(accRewardsPerAllocatedToken).to.be.gt(0) + expect(accRewardsForSubgraph).to.be.gt(0) + }) + + it('should handle case where only subgraph service has allocations', async function () { + // Setup: Create signal + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Only subgraph service has allocations + const tokensFromSubgraphService = toGRT('10000') + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, tokensFromSubgraphService) + + // Mine blocks + await helpers.mine(5) + + // Get rewards + const [accRewardsPerAllocatedToken] = await rewardsManager.getAccRewardsPerAllocatedToken(subgraphDeploymentID1) + + expect(accRewardsPerAllocatedToken).to.be.gt(0) + }) + + it('should return zero when neither issuer has allocations', async function () { + // Setup: Create signal but no allocations + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // No allocations from either issuer + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, 0) + + // Mine blocks + await helpers.mine(5) + + // Get rewards - should return 0 when no allocations + const [accRewardsPerAllocatedToken, accRewardsForSubgraph] = + await rewardsManager.getAccRewardsPerAllocatedToken(subgraphDeploymentID1) + + expect(accRewardsPerAllocatedToken).to.equal(0) + expect(accRewardsForSubgraph).to.be.gt(0) // Subgraph still accrues, but no per-token rewards + }) + }) + + describe('subgraph service with denylist and eligibility', function () { + it('should deny rewards from subgraph service when subgraph is on denylist', async function () { + // Setup denylist + await rewardsManager.connect(governor).setSubgraphAvailabilityOracle(governor.address) + await rewardsManager.connect(governor).setDenied(subgraphDeploymentID1, true) + + // Setup allocation + await mockSubgraphService.setAllocation( + allocationID1, + true, + indexer1.address, + subgraphDeploymentID1, + toGRT('12500'), + 0, + 0, + ) + + // Impersonate the mock subgraph service contract + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [mockSubgraphService.address], + }) + await network.provider.send('hardhat_setBalance', [mockSubgraphService.address, '0x1000000000000000000']) + + const mockSubgraphServiceSigner = await hre.ethers.getSigner(mockSubgraphService.address) + + // Take rewards should be denied + const tx = rewardsManager.connect(mockSubgraphServiceSigner).takeRewards(allocationID1) + await expect(tx).emit(rewardsManager, 'RewardsDenied').withArgs(indexer1.address, allocationID1) + + // Stop impersonating + await network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [mockSubgraphService.address], + }) + }) + + it('should deny rewards from subgraph service when indexer is ineligible', async function () { + // Setup REO that denies indexer1 + const MockRewardsEligibilityOracleFactory = await hre.ethers.getContractFactory( + 'contracts/tests/MockRewardsEligibilityOracle.sol:MockRewardsEligibilityOracle', + ) + const mockREO = await MockRewardsEligibilityOracleFactory.deploy(false) // Deny by default + await mockREO.deployed() + await rewardsManager.connect(governor).setRewardsEligibilityOracle(mockREO.address) + + // Setup: Create signal + const signalled1 = toGRT('1500') + await curation.connect(curator1).mint(subgraphDeploymentID1, signalled1, 0) + + // Setup allocation + const tokensAllocated = toGRT('12500') + await mockSubgraphService.setAllocation( + allocationID1, + true, + indexer1.address, + subgraphDeploymentID1, + tokensAllocated, + 0, + 0, + ) + + await mockSubgraphService.setSubgraphAllocatedTokens(subgraphDeploymentID1, tokensAllocated) + + // Mine blocks to accrue rewards + await helpers.mine(5) + + // Impersonate the mock subgraph service contract + await network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [mockSubgraphService.address], + }) + await network.provider.send('hardhat_setBalance', [mockSubgraphService.address, '0x1000000000000000000']) + + const mockSubgraphServiceSigner = await hre.ethers.getSigner(mockSubgraphService.address) + + // Take rewards should be denied due to eligibility + const tx = rewardsManager.connect(mockSubgraphServiceSigner).takeRewards(allocationID1) + await expect(tx).emit(rewardsManager, 'RewardsDeniedDueToEligibility') + + // Stop impersonating + await network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [mockSubgraphService.address], + }) + }) + }) + }) +}) diff --git a/packages/data-edge/contracts/DataEdge.sol b/packages/data-edge/contracts/DataEdge.sol index fc39b7386..8b02c3ce0 100644 --- a/packages/data-edge/contracts/DataEdge.sol +++ b/packages/data-edge/contracts/DataEdge.sol @@ -5,8 +5,10 @@ pragma solidity ^0.8.12; /// @title Data Edge contract is only used to store on-chain data, it does not /// perform execution. On-chain client services can read the data /// and decode the payload for different purposes. +/// @author Edge & Node +/// @notice Contract for storing on-chain data without execution contract DataEdge { - /// @dev Fallback function, accepts any payload + /// @notice Fallback function, accepts any payload fallback() external payable { // no-op } diff --git a/packages/data-edge/contracts/EventfulDataEdge.sol b/packages/data-edge/contracts/EventfulDataEdge.sol index d995be665..d3725f151 100644 --- a/packages/data-edge/contracts/EventfulDataEdge.sol +++ b/packages/data-edge/contracts/EventfulDataEdge.sol @@ -6,9 +6,14 @@ pragma solidity ^0.8.12; /// perform execution. On-chain client services can read the data /// and decode the payload for different purposes. /// NOTE: This version emits an event with the calldata. +/// @author Edge & Node +/// @notice Contract for storing on-chain data with event logging contract EventfulDataEdge { + /// @notice Emitted when data is received + /// @param data The calldata received by the contract event Log(bytes data); + /// @notice Accepts any payload and emits it as an event /// @dev Fallback function, accepts any payload fallback() external payable { emit Log(msg.data); diff --git a/packages/horizon/contracts/data-service/DataService.sol b/packages/horizon/contracts/data-service/DataService.sol index 6b14365d5..21205dbfe 100644 --- a/packages/horizon/contracts/data-service/DataService.sol +++ b/packages/horizon/contracts/data-service/DataService.sol @@ -9,6 +9,7 @@ import { ProvisionManager } from "./utilities/ProvisionManager.sol"; /** * @title DataService contract + * @author Edge & Node * @dev Implementation of the {IDataService} interface. * @notice This implementation provides base functionality for a data service: * - GraphDirectory, allows the data service to interact with Graph Horizon contracts @@ -32,6 +33,7 @@ import { ProvisionManager } from "./utilities/ProvisionManager.sol"; */ abstract contract DataService is GraphDirectory, ProvisionManager, DataServiceV1Storage, IDataService { /** + * @notice Constructor for the DataService contract * @dev Addresses in GraphDirectory are immutables, they can only be set in this constructor. * @param controller The address of the Graph Horizon controller contract. */ diff --git a/packages/horizon/contracts/data-service/DataServiceStorage.sol b/packages/horizon/contracts/data-service/DataServiceStorage.sol index df759b892..0b6d27e4b 100644 --- a/packages/horizon/contracts/data-service/DataServiceStorage.sol +++ b/packages/horizon/contracts/data-service/DataServiceStorage.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.27; /** * @title DataServiceStorage - * @dev This contract holds the storage variables for the DataService contract. + * @author Edge & Node + * @notice This contract holds the storage variables for the DataService contract. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/horizon/contracts/data-service/extensions/DataServiceFees.sol b/packages/horizon/contracts/data-service/extensions/DataServiceFees.sol index a82c15361..91f5c5f4e 100644 --- a/packages/horizon/contracts/data-service/extensions/DataServiceFees.sol +++ b/packages/horizon/contracts/data-service/extensions/DataServiceFees.sol @@ -12,6 +12,7 @@ import { DataServiceFeesV1Storage } from "./DataServiceFeesStorage.sol"; /** * @title DataServiceFees contract + * @author Edge & Node * @dev Implementation of the {IDataServiceFees} interface. * @notice Extension for the {IDataService} contract to handle payment collateralization * using a Horizon provision. See {IDataServiceFees} for more details. diff --git a/packages/horizon/contracts/data-service/extensions/DataServiceFeesStorage.sol b/packages/horizon/contracts/data-service/extensions/DataServiceFeesStorage.sol index 78d826f03..bafb8fe52 100644 --- a/packages/horizon/contracts/data-service/extensions/DataServiceFeesStorage.sol +++ b/packages/horizon/contracts/data-service/extensions/DataServiceFeesStorage.sol @@ -7,6 +7,8 @@ import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/interna /** * @title Storage layout for the {DataServiceFees} extension contract. + * @author Edge & Node + * @notice Storage layout for the DataServiceFees extension contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/horizon/contracts/data-service/extensions/DataServicePausable.sol b/packages/horizon/contracts/data-service/extensions/DataServicePausable.sol index a4c60f7db..b1bd4203a 100644 --- a/packages/horizon/contracts/data-service/extensions/DataServicePausable.sol +++ b/packages/horizon/contracts/data-service/extensions/DataServicePausable.sol @@ -8,6 +8,7 @@ import { DataService } from "../DataService.sol"; /** * @title DataServicePausable contract + * @author Edge & Node * @dev Implementation of the {IDataServicePausable} interface. * @notice Extension for the {IDataService} contract, adds pausing functionality * to the data service. Pausing is controlled by privileged accounts called diff --git a/packages/horizon/contracts/data-service/extensions/DataServicePausableUpgradeable.sol b/packages/horizon/contracts/data-service/extensions/DataServicePausableUpgradeable.sol index 2ca8e09c6..ad792f914 100644 --- a/packages/horizon/contracts/data-service/extensions/DataServicePausableUpgradeable.sol +++ b/packages/horizon/contracts/data-service/extensions/DataServicePausableUpgradeable.sol @@ -8,8 +8,9 @@ import { DataService } from "../DataService.sol"; /** * @title DataServicePausableUpgradeable contract + * @author Edge & Node + * @notice Upgradeable version of the {DataServicePausable} contract. * @dev Implementation of the {IDataServicePausable} interface. - * @dev Upgradeable version of the {DataServicePausable} contract. * @dev This contract inherits from {DataService} which needs to be initialized, please see * {DataService} for detailed instructions. * @custom:security-contact Please email security+contracts@thegraph.com if you find any diff --git a/packages/horizon/contracts/data-service/extensions/DataServiceRescuable.sol b/packages/horizon/contracts/data-service/extensions/DataServiceRescuable.sol index 0bb600a01..a6af01533 100644 --- a/packages/horizon/contracts/data-service/extensions/DataServiceRescuable.sol +++ b/packages/horizon/contracts/data-service/extensions/DataServiceRescuable.sol @@ -12,8 +12,9 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s /** * @title Rescuable contract - * @dev Allows a contract to have a function to rescue tokens sent by mistake. - * The contract must implement the external rescueTokens function or similar, + * @author Edge & Node + * @notice Allows a contract to have a function to rescue tokens sent by mistake. + * @dev The contract must implement the external rescueTokens function or similar, * that calls this contract's _rescueTokens. * @dev Note that this extension does not provide an external function to set * rescuers. This should be implemented in the derived contract. @@ -63,7 +64,7 @@ abstract contract DataServiceRescuable is DataService, IDataServiceRescuable { } /** - * @dev Allows rescuing tokens sent to this contract + * @notice Allows rescuing tokens sent to this contract * @param _to Destination address to send the tokens * @param _token Address of the token being rescued * @param _tokens Amount of tokens to pull diff --git a/packages/horizon/contracts/data-service/libraries/ProvisionTracker.sol b/packages/horizon/contracts/data-service/libraries/ProvisionTracker.sol index 06612913d..42f4a7de9 100644 --- a/packages/horizon/contracts/data-service/libraries/ProvisionTracker.sol +++ b/packages/horizon/contracts/data-service/libraries/ProvisionTracker.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol"; /** * @title ProvisionTracker library + * @author Edge & Node * @notice A library to facilitate tracking of "used tokens" on Graph Horizon provisions. This can be used to * ensure data services have enough economic security (provisioned stake) to back the payments they collect for * their services. diff --git a/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol b/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol index db5a9e8f5..9d0db415b 100644 --- a/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol +++ b/packages/horizon/contracts/data-service/utilities/ProvisionManager.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events +// solhint-disable gas-strict-inequalities + import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol"; import { UintRange } from "../../libraries/UintRange.sol"; @@ -12,6 +16,7 @@ import { ProvisionManagerV1Storage } from "./ProvisionManagerStorage.sol"; /** * @title ProvisionManager contract + * @author Edge & Node * @notice A helper contract that implements several provision management functions. * @dev Provides utilities to verify provision parameters are within an acceptable range. Each * parameter has an overridable setter and getter for the validity range, and a checker that reverts diff --git a/packages/horizon/contracts/data-service/utilities/ProvisionManagerStorage.sol b/packages/horizon/contracts/data-service/utilities/ProvisionManagerStorage.sol index 5931c66e5..d2d3495ba 100644 --- a/packages/horizon/contracts/data-service/utilities/ProvisionManagerStorage.sol +++ b/packages/horizon/contracts/data-service/utilities/ProvisionManagerStorage.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.27; /** * @title Storage layout for the {ProvisionManager} helper contract. + * @author Edge & Node + * @notice Storage layout for the ProvisionManager helper contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/horizon/contracts/libraries/Denominations.sol b/packages/horizon/contracts/libraries/Denominations.sol index 46cff3516..abd0ac9a6 100644 --- a/packages/horizon/contracts/libraries/Denominations.sol +++ b/packages/horizon/contracts/libraries/Denominations.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.27; /** * @title Denominations library - * @dev Provides a list of ground denominations for those tokens that cannot be represented by an ERC20. + * @author Edge & Node + * @notice Provides a list of ground denominations for those tokens that cannot be represented by an ERC20 * For now, the only needed is the native token that could be ETH, MATIC, or other depending on the layer being operated. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. diff --git a/packages/horizon/contracts/libraries/LibFixedMath.sol b/packages/horizon/contracts/libraries/LibFixedMath.sol index 704617b40..2468721b2 100644 --- a/packages/horizon/contracts/libraries/LibFixedMath.sol +++ b/packages/horizon/contracts/libraries/LibFixedMath.sol @@ -20,8 +20,12 @@ pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + /** * @title LibFixedMath + * @author Edge & Node * @notice This library provides fixed-point arithmetic operations. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. @@ -36,12 +40,20 @@ library LibFixedMath { // -63.875 int256 private constant EXP_MIN_VAL = -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000); - /// @dev Get one as a fixed-point number. + /** + * @notice Get one as a fixed-point number + * @return f The fixed-point representation of one + */ function one() internal pure returns (int256 f) { f = FIXED_1; } - /// @dev Returns the addition of two fixed point numbers, reverting on overflow. + /** + * @notice Returns the subtraction of two fixed point numbers, reverting on overflow + * @param a The first fixed point number + * @param b The second fixed point number to subtract + * @return c The result of a - b + */ function sub(int256 a, int256 b) internal pure returns (int256 c) { if (b == MIN_FIXED_VAL) { revert("out-of-bounds"); @@ -49,19 +61,34 @@ library LibFixedMath { c = _add(a, -b); } - /// @dev Returns the multiplication of two fixed point numbers, reverting on overflow. + /** + * @notice Returns the multiplication of two fixed point numbers, reverting on overflow + * @param a The first fixed point number + * @param b The second fixed point number + * @return c The result of a * b + */ function mul(int256 a, int256 b) internal pure returns (int256 c) { c = _mul(a, b) / FIXED_1; } - /// @dev Performs (a * n) / d, without scaling for precision. + /** + * @notice Performs (a * n) / d, without scaling for precision + * @param a The first fixed point number + * @param n The numerator + * @param d The denominator + * @return c The result of (a * n) / d + */ function mulDiv(int256 a, int256 n, int256 d) internal pure returns (int256 c) { c = _div(_mul(a, n), d); } - /// @dev Returns the unsigned integer result of multiplying a fixed-point - /// number with an integer, reverting if the multiplication overflows. - /// Negative results are clamped to zero. + /** + * @notice Returns the unsigned integer result of multiplying a fixed-point number with an integer + * @dev Negative results are clamped to zero. Reverts if the multiplication overflows. + * @param f Fixed-point number + * @param u Unsigned integer + * @return Unsigned integer result, clamped to zero if negative + */ function uintMul(int256 f, uint256 u) internal pure returns (uint256) { if (int256(u) < int256(0)) { revert("out-of-bounds"); @@ -73,17 +100,30 @@ library LibFixedMath { return uint256(uint256(c) >> 127); } - /// @dev Convert signed `n` / `d` to a fixed-point number. + /** + * @notice Convert signed `n` / `d` to a fixed-point number + * @param n Numerator + * @param d Denominator + * @return f Fixed-point representation of n/d + */ function toFixed(int256 n, int256 d) internal pure returns (int256 f) { f = _div(_mul(n, FIXED_1), d); } - /// @dev Convert a fixed-point number to an integer. + /** + * @notice Convert a fixed-point number to an integer + * @param f Fixed-point number + * @return n Integer representation + */ function toInteger(int256 f) internal pure returns (int256 n) { return f / FIXED_1; } - /// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + /** + * @notice Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1 + * @param x Fixed-point number to compute exponent for + * @return r The natural exponent of x + */ function exp(int256 x) internal pure returns (int256 r) { if (x < EXP_MIN_VAL) { // Saturate to zero below EXP_MIN_VAL. @@ -205,7 +245,12 @@ library LibFixedMath { } } - /// @dev Returns the multiplication two numbers, reverting on overflow. + /** + * @notice Returns the multiplication of two numbers, reverting on overflow + * @param a First number + * @param b Second number + * @return c The result of a * b + */ function _mul(int256 a, int256 b) private pure returns (int256 c) { if (a == 0 || b == 0) { return 0; @@ -218,7 +263,12 @@ library LibFixedMath { } } - /// @dev Returns the division of two numbers, reverting on division by zero. + /** + * @notice Returns the division of two numbers, reverting on division by zero + * @param a Dividend + * @param b Divisor + * @return c The result of a / b + */ function _div(int256 a, int256 b) private pure returns (int256 c) { if (b == 0) { revert("overflow"); @@ -231,7 +281,12 @@ library LibFixedMath { } } - /// @dev Adds two numbers, reverting on overflow. + /** + * @notice Adds two numbers, reverting on overflow + * @param a First number + * @param b Second number + * @return c The result of a + b + */ function _add(int256 a, int256 b) private pure returns (int256 c) { unchecked { c = a + b; diff --git a/packages/horizon/contracts/libraries/LinkedList.sol b/packages/horizon/contracts/libraries/LinkedList.sol index 9d3305329..083b1f436 100644 --- a/packages/horizon/contracts/libraries/LinkedList.sol +++ b/packages/horizon/contracts/libraries/LinkedList.sol @@ -2,10 +2,14 @@ pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, gas-strict-inequalities + import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/internal/ILinkedList.sol"; /** * @title LinkedList library + * @author Edge & Node * @notice A library to manage singly linked lists. * * The library makes no assumptions about the contents of the items, the only diff --git a/packages/horizon/contracts/libraries/MathUtils.sol b/packages/horizon/contracts/libraries/MathUtils.sol index fc81e9608..6c0a09a1a 100644 --- a/packages/horizon/contracts/libraries/MathUtils.sol +++ b/packages/horizon/contracts/libraries/MathUtils.sol @@ -1,23 +1,29 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + pragma solidity 0.8.27; /** * @title MathUtils Library + * @author Edge & Node * @notice A collection of functions to perform math operations * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ library MathUtils { /** - * @dev Calculates the weighted average of two values pondering each of these - * values based on configured weights. The contribution of each value N is + * @notice Calculates the weighted average of two values pondering each of these + * values based on configured weights + * @dev The contribution of each value N is * weightN/(weightA + weightB). The calculation rounds up to ensure the result * is always equal or greater than the smallest of the two values. * @param valueA The amount for value A * @param weightA The weight to use for value A * @param valueB The amount for value B * @param weightB The weight to use for value B + * @return The weighted average result */ function weightedAverageRoundingUp( uint256 valueA, @@ -29,7 +35,7 @@ library MathUtils { } /** - * @dev Returns the minimum of two numbers. + * @notice Returns the minimum of two numbers * @param x The first number * @param y The second number * @return The minimum of the two numbers @@ -39,7 +45,7 @@ library MathUtils { } /** - * @dev Returns the difference between two numbers or zero if negative. + * @notice Returns the difference between two numbers or zero if negative * @param x The first number * @param y The second number * @return The difference between the two numbers or zero if negative diff --git a/packages/horizon/contracts/libraries/PPMMath.sol b/packages/horizon/contracts/libraries/PPMMath.sol index a7966c91d..998a912e8 100644 --- a/packages/horizon/contracts/libraries/PPMMath.sol +++ b/packages/horizon/contracts/libraries/PPMMath.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + /** * @title PPMMath library + * @author Edge & Node * @notice A library for handling calculations with parts per million (PPM) amounts. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. diff --git a/packages/horizon/contracts/libraries/UintRange.sol b/packages/horizon/contracts/libraries/UintRange.sol index 69d3f5d8a..7c9bdfdd8 100644 --- a/packages/horizon/contracts/libraries/UintRange.sol +++ b/packages/horizon/contracts/libraries/UintRange.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + /** * @title UintRange library + * @author Edge & Node * @notice A library for handling range checks on uint256 values. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. diff --git a/packages/horizon/contracts/mocks/ControllerMock.sol b/packages/horizon/contracts/mocks/ControllerMock.sol index 638387f0c..415e48c5e 100644 --- a/packages/horizon/contracts/mocks/ControllerMock.sol +++ b/packages/horizon/contracts/mocks/ControllerMock.sol @@ -7,22 +7,30 @@ import { IManaged } from "@graphprotocol/interfaces/contracts/contracts/governan /** * @title Graph Controller contract (mock) + * @author Edge & Node + * @notice Mock implementation of the Graph Controller contract for testing * @dev Controller is a registry of contracts for convenience. Inspired by Livepeer: * https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol */ contract ControllerMock is IController { /// @dev Track contract ids to contract proxy address mapping(bytes32 contractName => address contractAddress) private _registry; + + /// @notice Address of the governor address public governor; bool internal _paused; bool internal _partialPaused; address internal _pauseGuardian; - /// Emitted when the proxy address for a protocol contract has been set - event SetContractProxy(bytes32 indexed id, address contractAddress); + /** + * @notice Emitted when the proxy address for a protocol contract has been set + * @param id The contract identifier + * @param contractAddress The new contract address + */ + event SetContractProxy(bytes32 indexed id, address indexed contractAddress); /** - * Constructor for the Controller mock + * @notice Constructor for the Controller mock * @param governor_ Address of the governor */ constructor(address governor_) { diff --git a/packages/horizon/contracts/mocks/CurationMock.sol b/packages/horizon/contracts/mocks/CurationMock.sol index ea3df0587..9de0fea16 100644 --- a/packages/horizon/contracts/mocks/CurationMock.sol +++ b/packages/horizon/contracts/mocks/CurationMock.sol @@ -2,17 +2,38 @@ pragma solidity 0.8.27; +/** + * @title CurationMock + * @author Edge & Node + * @notice Mock implementation of curation functionality for testing + */ contract CurationMock { + /// @notice Mapping of subgraph deployment ID to curation tokens mapping(bytes32 subgraphDeploymentID => uint256 tokens) public curation; + /** + * @notice Signal curation tokens for a subgraph deployment + * @param subgraphDeploymentID The subgraph deployment ID + * @param tokens The amount of tokens to signal + */ function signal(bytes32 subgraphDeploymentID, uint256 tokens) public { curation[subgraphDeploymentID] += tokens; } + /** + * @notice Check if a subgraph deployment is curated + * @param subgraphDeploymentID The subgraph deployment ID + * @return True if the subgraph deployment has curation tokens + */ function isCurated(bytes32 subgraphDeploymentID) public view returns (bool) { return curation[subgraphDeploymentID] != 0; } + /** + * @notice Collect curation tokens for a subgraph deployment + * @param subgraphDeploymentID The subgraph deployment ID + * @param tokens The amount of tokens to collect + */ function collect(bytes32 subgraphDeploymentID, uint256 tokens) external { curation[subgraphDeploymentID] += tokens; } diff --git a/packages/horizon/contracts/mocks/Dummy.sol b/packages/horizon/contracts/mocks/Dummy.sol index e6a575d0f..72b4e1a67 100644 --- a/packages/horizon/contracts/mocks/Dummy.sol +++ b/packages/horizon/contracts/mocks/Dummy.sol @@ -2,4 +2,9 @@ pragma solidity 0.8.27; +/** + * @title Dummy + * @author Edge & Node + * @notice Empty dummy contract for testing purposes + */ contract Dummy {} diff --git a/packages/horizon/contracts/mocks/EpochManagerMock.sol b/packages/horizon/contracts/mocks/EpochManagerMock.sol index 4c00fb29c..4030dc19c 100644 --- a/packages/horizon/contracts/mocks/EpochManagerMock.sol +++ b/packages/horizon/contracts/mocks/EpochManagerMock.sol @@ -4,16 +4,29 @@ pragma solidity 0.8.27; import { IEpochManager } from "@graphprotocol/interfaces/contracts/contracts/epochs/IEpochManager.sol"; +/** + * @title EpochManagerMock + * @author Edge & Node + * @notice Mock implementation of the EpochManager for testing + */ contract EpochManagerMock is IEpochManager { // -- Variables -- + /// @notice Length of an epoch in blocks uint256 public epochLength; + /// @notice Last epoch that was run uint256 public lastRunEpoch; + /// @notice Last epoch when the length was updated uint256 public lastLengthUpdateEpoch; + /// @notice Block number when the length was last updated uint256 public lastLengthUpdateBlock; // -- Configuration -- + /** + * @notice Set the epoch length + * @param epochLength_ New epoch length in blocks + */ function setEpochLength(uint256 epochLength_) public { lastLengthUpdateEpoch = 1; lastLengthUpdateBlock = blockNum(); @@ -22,41 +35,78 @@ contract EpochManagerMock is IEpochManager { // -- Epochs + /** + * @notice Run the current epoch + */ function runEpoch() public { lastRunEpoch = currentEpoch(); } // -- Getters -- + /** + * @notice Check if the current epoch has been run + * @return True if the current epoch has been run + */ function isCurrentEpochRun() public view returns (bool) { return lastRunEpoch == currentEpoch(); } + /** + * @notice Get the current block number + * @return The current block number + */ function blockNum() public view returns (uint256) { return block.number; } + /** + * @notice Get the hash of a specific block + * @param block_ Block number to get hash for + * @return The block hash + */ function blockHash(uint256 block_) public view returns (bytes32) { return blockhash(block_); } + /** + * @notice Get the current epoch number + * @return The current epoch number + */ function currentEpoch() public view returns (uint256) { return lastLengthUpdateEpoch + epochsSinceUpdate(); } + /** + * @notice Get the block number when the current epoch started + * @return The block number when the current epoch started + */ function currentEpochBlock() public view returns (uint256) { return lastLengthUpdateBlock + (epochsSinceUpdate() * epochLength); } + /** + * @notice Get the number of blocks since the current epoch started + * @return The number of blocks since the current epoch started + */ function currentEpochBlockSinceStart() public view returns (uint256) { return blockNum() - currentEpochBlock(); } + /** + * @notice Get the number of epochs since a given epoch + * @param epoch_ The epoch to compare against + * @return The number of epochs since the given epoch + */ function epochsSince(uint256 epoch_) public view returns (uint256) { uint256 epoch = currentEpoch(); return epoch_ < epoch ? (epoch - epoch_) : 0; } + /** + * @notice Get the number of epochs since the last length update + * @return The number of epochs since the last length update + */ function epochsSinceUpdate() public view returns (uint256) { return (blockNum() - lastLengthUpdateBlock) / epochLength; } diff --git a/packages/horizon/contracts/mocks/MockGRTToken.sol b/packages/horizon/contracts/mocks/MockGRTToken.sol index 235999ae5..3186aeb1c 100644 --- a/packages/horizon/contracts/mocks/MockGRTToken.sol +++ b/packages/horizon/contracts/mocks/MockGRTToken.sol @@ -2,29 +2,67 @@ pragma solidity 0.8.27; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; +/** + * @title MockGRTToken + * @author Edge & Node + * @notice Mock implementation of the Graph Token for testing + */ contract MockGRTToken is ERC20, IGraphToken { + /** + * @notice Constructor for the MockGRTToken + */ constructor() ERC20("Graph Token", "GRT") {} + /** + * @notice Burn tokens from the caller's account + * @param tokens Amount of tokens to burn + */ function burn(uint256 tokens) external { _burn(msg.sender, tokens); } + /** + * @notice Burn tokens from a specific account + * @param from Account to burn tokens from + * @param tokens Amount of tokens to burn + */ function burnFrom(address from, uint256 tokens) external { _burn(from, tokens); } // -- Mint Admin -- + /** + * @notice Add a minter (mock implementation - does nothing) + * @param account Account to add as minter + */ function addMinter(address account) external {} + /** + * @notice Remove a minter (mock implementation - does nothing) + * @param account Account to remove as minter + */ function removeMinter(address account) external {} + /** + * @notice Renounce minter role (mock implementation - does nothing) + */ function renounceMinter() external {} // -- Permit -- + /** + * @notice Permit function for gasless approvals (mock implementation - does nothing) + * @param owner Token owner + * @param spender Spender address + * @param value Amount to approve + * @param deadline Deadline for the permit + * @param v Recovery byte of the signature + * @param r First 32 bytes of the signature + * @param s Second 32 bytes of the signature + */ function permit( address owner, address spender, @@ -37,12 +75,34 @@ contract MockGRTToken is ERC20, IGraphToken { // -- Allowance -- + /** + * @notice Increase allowance (mock implementation - does nothing) + * @param spender Spender address + * @param addedValue Amount to add to allowance + * @return Always returns false in mock + */ function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {} + /** + * @notice Decrease allowance (mock implementation - does nothing) + * @param spender Spender address + * @param subtractedValue Amount to subtract from allowance + * @return Always returns false in mock + */ function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {} + /** + * @notice Check if an account is a minter (mock implementation - always returns false) + * @param account Account to check + * @return Always returns false in mock + */ function isMinter(address account) external view returns (bool) {} + /** + * @notice Mint tokens to an account + * @param to Account to mint tokens to + * @param tokens Amount of tokens to mint + */ function mint(address to, uint256 tokens) public { _mint(to, tokens); } diff --git a/packages/horizon/contracts/mocks/RewardsManagerMock.sol b/packages/horizon/contracts/mocks/RewardsManagerMock.sol index 300e7efe2..2883f0175 100644 --- a/packages/horizon/contracts/mocks/RewardsManagerMock.sol +++ b/packages/horizon/contracts/mocks/RewardsManagerMock.sol @@ -4,23 +4,57 @@ pragma solidity 0.8.27; import { MockGRTToken } from "./MockGRTToken.sol"; +/** + * @title RewardsManagerMock + * @author Edge & Node + * @notice Mock implementation of the RewardsManager for testing + */ contract RewardsManagerMock { // -- Variables -- + /// @notice The mock GRT token contract MockGRTToken public token; uint256 private _rewards; // -- Constructor -- + /** + * @notice Constructor for the RewardsManager mock + * @param token_ The mock GRT token contract + * @param rewards The amount of rewards to distribute + */ constructor(MockGRTToken token_, uint256 rewards) { token = token_; _rewards = rewards; } - function takeRewards(address) external returns (uint256) { + /** + * @notice Take rewards for an allocation + * @param allocationID The allocation ID (unused in this mock) + * @return The amount of rewards taken + */ + function takeRewards(address allocationID) external returns (uint256) { + allocationID; // silence unused variable warning token.mint(msg.sender, _rewards); return _rewards; } - function onSubgraphAllocationUpdate(bytes32) public returns (uint256) {} - function onSubgraphSignalUpdate(bytes32 subgraphDeploymentID) external returns (uint256) {} + /** + * @notice Handle subgraph allocation update (mock implementation) + * @param subgraphDeploymentID The subgraph deployment ID (unused in this mock) + * @return Always returns 0 in mock + */ + function onSubgraphAllocationUpdate(bytes32 subgraphDeploymentID) public pure returns (uint256) { + subgraphDeploymentID; // silence unused variable warning + return 0; + } + + /** + * @notice Handle subgraph signal update (mock implementation) + * @param subgraphDeploymentID The subgraph deployment ID (unused in this mock) + * @return Always returns 0 in mock + */ + function onSubgraphSignalUpdate(bytes32 subgraphDeploymentID) external pure returns (uint256) { + subgraphDeploymentID; // silence unused variable warning + return 0; + } } diff --git a/packages/horizon/contracts/payments/GraphPayments.sol b/packages/horizon/contracts/payments/GraphPayments.sol index e82758b0e..144a2daa1 100644 --- a/packages/horizon/contracts/payments/GraphPayments.sol +++ b/packages/horizon/contracts/payments/GraphPayments.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines + +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol"; @@ -14,6 +17,7 @@ import { GraphDirectory } from "../utilities/GraphDirectory.sol"; /** * @title GraphPayments contract + * @author Edge & Node * @notice This contract is part of the Graph Horizon payments protocol. It's designed * to pull funds (GRT) from the {PaymentsEscrow} and distribute them according to a * set of pre established rules. diff --git a/packages/horizon/contracts/payments/PaymentsEscrow.sol b/packages/horizon/contracts/payments/PaymentsEscrow.sol index bcced0412..50a5386c9 100644 --- a/packages/horizon/contracts/payments/PaymentsEscrow.sol +++ b/packages/horizon/contracts/payments/PaymentsEscrow.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol"; @@ -13,6 +16,7 @@ import { GraphDirectory } from "../utilities/GraphDirectory.sol"; /** * @title PaymentsEscrow contract + * @author Edge & Node * @dev Implements the {IPaymentsEscrow} interface * @notice This contract is part of the Graph Horizon payments protocol. It holds the funds (GRT) * for payments made through the payments protocol for services provided diff --git a/packages/horizon/contracts/payments/collectors/GraphTallyCollector.sol b/packages/horizon/contracts/payments/collectors/GraphTallyCollector.sol index 0ede499e0..eb33d931c 100644 --- a/packages/horizon/contracts/payments/collectors/GraphTallyCollector.sol +++ b/packages/horizon/contracts/payments/collectors/GraphTallyCollector.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-small-strings +// solhint-disable gas-strict-inequalities +// solhint-disable function-max-lines + import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; import { IGraphTallyCollector } from "@graphprotocol/interfaces/contracts/horizon/IGraphTallyCollector.sol"; import { IPaymentsCollector } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsCollector.sol"; @@ -14,6 +19,7 @@ import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /** * @title GraphTallyCollector contract + * @author Edge & Node * @dev Implements the {IGraphTallyCollector}, {IPaymentCollector} and {IAuthorizable} interfaces. * @notice A payments collector contract that can be used to collect payments using a GraphTally RAV (Receipt Aggregate Voucher). * @dev Note that the contract expects the RAV aggregate value to be monotonically increasing, each successive RAV for the same @@ -185,9 +191,9 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall } /** - * @dev Recovers the signer address of a signed ReceiptAggregateVoucher (RAV). - * @param _signedRAV The SignedRAV containing the RAV and its signature. - * @return The address of the signer. + * @notice Recovers the signer address of a signed ReceiptAggregateVoucher (RAV) + * @param _signedRAV The SignedRAV containing the RAV and its signature + * @return The address of the signer */ function _recoverRAVSigner(SignedRAV memory _signedRAV) private view returns (address) { bytes32 messageHash = _encodeRAV(_signedRAV.rav); @@ -195,9 +201,9 @@ contract GraphTallyCollector is EIP712, GraphDirectory, Authorizable, IGraphTall } /** - * @dev Computes the hash of a ReceiptAggregateVoucher (RAV). - * @param _rav The RAV for which to compute the hash. - * @return The hash of the RAV. + * @notice Computes the hash of a ReceiptAggregateVoucher (RAV) + * @param _rav The RAV for which to compute the hash + * @return The hash of the RAV */ function _encodeRAV(ReceiptAggregateVoucher memory _rav) private view returns (bytes32) { return diff --git a/packages/horizon/contracts/staking/HorizonStaking.sol b/packages/horizon/contracts/staking/HorizonStaking.sol index ff675fdd6..73f48c354 100644 --- a/packages/horizon/contracts/staking/HorizonStaking.sol +++ b/packages/horizon/contracts/staking/HorizonStaking.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities +// solhint-disable gas-increment-by-one +// solhint-disable function-max-lines + pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStakingMain } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol"; import { IHorizonStakingExtension } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; @@ -17,6 +22,7 @@ import { HorizonStakingBase } from "./HorizonStakingBase.sol"; /** * @title HorizonStaking contract + * @author Edge & Node * @notice The {HorizonStaking} contract allows service providers to stake and provision tokens to verifiers to be used * as economic security for a service. It also allows delegators to delegate towards a service provider provision. * @dev Implements the {IHorizonStakingMain} interface. @@ -69,11 +75,10 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { } /** - * @dev The staking contract is upgradeable however we still use the constructor to set - * a few immutable variables. - * @param controller The address of the Graph controller contract. - * @param stakingExtensionAddress The address of the staking extension contract. - * @param subgraphDataServiceAddress The address of the subgraph data service. + * @notice The staking contract is upgradeable however we still use the constructor to set a few immutable variables + * @param controller The address of the Graph controller contract + * @param stakingExtensionAddress The address of the staking extension contract + * @param subgraphDataServiceAddress The address of the subgraph data service */ constructor( address controller, @@ -88,8 +93,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { * @dev This function does not return to its internal call site, it will return directly to the * external caller. */ - // solhint-disable-next-line payable-fallback, no-complex-fallback fallback() external { + // solhint-disable-previous-line payable-fallback, no-complex-fallback address extensionImpl = STAKING_EXTENSION_ADDRESS; // solhint-disable-next-line no-inline-assembly assembly { @@ -681,8 +686,8 @@ contract HorizonStaking is HorizonStakingBase, IHorizonStakingMain { * @dev TRANSITION PERIOD: During the transition period, only the subgraph data service can be used as a verifier. This * prevents an escape hatch for legacy allocation stake. * @param _serviceProvider The service provider address - * @param _verifier The verifier address for which the tokens are provisioned (who will be able to slash the tokens) * @param _tokens The amount of tokens that will be locked and slashable + * @param _verifier The verifier address for which the tokens are provisioned (who will be able to slash the tokens) * @param _maxVerifierCut The maximum cut, expressed in PPM, that a verifier can transfer instead of burning when slashing * @param _thawingPeriod The period in seconds that the tokens will be thawing before they can be removed from the provision */ diff --git a/packages/horizon/contracts/staking/HorizonStakingBase.sol b/packages/horizon/contracts/staking/HorizonStakingBase.sol index 2428e1e98..9c52a2171 100644 --- a/packages/horizon/contracts/staking/HorizonStakingBase.sol +++ b/packages/horizon/contracts/staking/HorizonStakingBase.sol @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + pragma solidity 0.8.27; import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol"; @@ -17,6 +20,7 @@ import { HorizonStakingV1Storage } from "./HorizonStakingStorage.sol"; /** * @title HorizonStakingBase contract + * @author Edge & Node * @notice This contract is the base staking contract implementing storage getters for both internal * and external use. * @dev Implementation of the {IHorizonStakingBase} interface. @@ -42,10 +46,9 @@ abstract contract HorizonStakingBase is address internal immutable SUBGRAPH_DATA_SERVICE_ADDRESS; /** - * @dev The staking contract is upgradeable however we still use the constructor to set - * a few immutable variables. - * @param controller The address of the Graph controller contract. - * @param subgraphDataServiceAddress The address of the subgraph data service. + * @notice The staking contract is upgradeable however we still use the constructor to set a few immutable variables + * @param controller The address of the Graph controller contract + * @param subgraphDataServiceAddress The address of the subgraph data service */ constructor(address controller, address subgraphDataServiceAddress) Managed(controller) { SUBGRAPH_DATA_SERVICE_ADDRESS = subgraphDataServiceAddress; diff --git a/packages/horizon/contracts/staking/HorizonStakingExtension.sol b/packages/horizon/contracts/staking/HorizonStakingExtension.sol index 3480a5c4d..b1adcde0d 100644 --- a/packages/horizon/contracts/staking/HorizonStakingExtension.sol +++ b/packages/horizon/contracts/staking/HorizonStakingExtension.sol @@ -2,8 +2,11 @@ pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curation/ICuration.sol"; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStakingExtension } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol"; import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol"; @@ -16,6 +19,7 @@ import { HorizonStakingBase } from "./HorizonStakingBase.sol"; /** * @title Horizon Staking extension contract + * @author Edge & Node * @notice The {HorizonStakingExtension} contract implements the legacy functionality required to support the transition * to the Horizon Staking contract. It allows indexers to close allocations and collect pending query fees, but it * does not allow for the creation of new allocations. This should allow indexers to migrate to a subgraph data service @@ -39,10 +43,9 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev The staking contract is upgradeable however we still use the constructor to set - * a few immutable variables. - * @param controller The address of the Graph controller contract. - * @param subgraphDataServiceAddress The address of the subgraph data service. + * @notice The staking contract is upgradeable however we still use the constructor to set a few immutable variables + * @param controller The address of the Graph controller contract + * @param subgraphDataServiceAddress The address of the subgraph data service */ constructor( address controller, @@ -265,7 +268,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Collect tax to burn for an amount of tokens. + * @notice Collect tax to burn for an amount of tokens * @param _tokens Total tokens received used to calculate the amount of tax to collect * @param _percentage Percentage of tokens to burn as tax * @return Amount of tax charged @@ -277,7 +280,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Triggers an update of rewards due to a change in allocations. + * @notice Triggers an update of rewards due to a change in allocations * @param _subgraphDeploymentID Subgraph deployment updated */ function _updateRewards(bytes32 _subgraphDeploymentID) private { @@ -285,7 +288,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Assign rewards for the closed allocation to indexer and delegators. + * @notice Assign rewards for the closed allocation to indexer and delegators * @param _allocationID Allocation * @param _indexer Address of the indexer that did the allocation */ @@ -307,7 +310,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Send rewards to the appropriate destination. + * @notice Send rewards to the appropriate destination * @param _tokens Number of rewards tokens * @param _beneficiary Address of the beneficiary of rewards * @param _restake Whether to restake or not @@ -326,7 +329,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Close an allocation and free the staked tokens. + * @notice Close an allocation and free the staked tokens * @param _allocationID The allocation identifier * @param _poi Proof of indexing submitted for the allocated period */ @@ -390,8 +393,8 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Collect the delegation rewards for query fees. - * This function will assign the collected fees to the delegation pool. + * @notice Collect the delegation rewards for query fees + * @dev 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 @@ -408,8 +411,8 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Collect the delegation rewards for indexing. - * This function will assign the collected fees to the delegation pool. + * @notice Collect the delegation rewards for indexing + * @dev 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 @@ -426,8 +429,8 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @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 + * @notice Collect the curation fees for a subgraph deployment from an amount of tokens + * @dev This function transfer curation fees to the Curation contract by calling Curation.collect * @param _subgraphDeploymentID Subgraph deployment to which the curation fees are related * @param _tokens Total tokens received used to calculate the amount of fees to collect * @param _curationCut Percentage of tokens to collect as fees @@ -461,7 +464,7 @@ contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension } /** - * @dev Return the current state of an allocation + * @notice Return the current state of an allocation * @param _allocationID Allocation identifier * @return AllocationState enum with the state of the allocation */ diff --git a/packages/horizon/contracts/staking/HorizonStakingStorage.sol b/packages/horizon/contracts/staking/HorizonStakingStorage.sol index 19dc65f11..5f63af9df 100644 --- a/packages/horizon/contracts/staking/HorizonStakingStorage.sol +++ b/packages/horizon/contracts/staking/HorizonStakingStorage.sol @@ -11,7 +11,8 @@ import { ILinkedList } from "@graphprotocol/interfaces/contracts/horizon/interna /** * @title HorizonStakingV1Storage - * @notice This contract holds all the storage variables for the Staking contract. + * @author Edge & Node + * @notice This contract holds all the storage variables for the Staking contract * @dev Deprecated variables are kept to support the transition to Horizon Staking. * They can eventually be collapsed into a single storage slot. * @custom:security-contact Please email security+contracts@thegraph.com if you find any diff --git a/packages/horizon/contracts/staking/libraries/ExponentialRebates.sol b/packages/horizon/contracts/staking/libraries/ExponentialRebates.sol index b137079b3..974e7197b 100644 --- a/packages/horizon/contracts/staking/libraries/ExponentialRebates.sol +++ b/packages/horizon/contracts/staking/libraries/ExponentialRebates.sol @@ -6,6 +6,7 @@ import { LibFixedMath } from "../../libraries/LibFixedMath.sol"; /** * @title ExponentialRebates library + * @author Edge & Node * @notice A library to compute query fee rebates using an exponential formula * @dev This is only used for backwards compatibility in HorizonStaking, and should * be removed after the transition period. @@ -16,21 +17,22 @@ library ExponentialRebates { /// @dev Maximum value of the exponent for which to compute the exponential before clamping to zero. uint32 private constant MAX_EXPONENT = 15; - /// @dev The exponential formula used to compute fee-based rewards for - /// staking pools in a given epoch. This function does not perform - /// bounds checking on the inputs, but the following conditions - /// need to be true: - /// 0 <= alphaNumerator / alphaDenominator <= 1 - /// 0 < lambdaNumerator / lambdaDenominator - /// The exponential rebates function has the form: - /// `(1 - alpha * exp ^ (-lambda * stake / fees)) * fees` - /// @param fees Fees generated by indexer in the staking pool. - /// @param stake Stake attributed to the indexer in the staking pool. - /// @param alphaNumerator Numerator of `alpha` in the rebates function. - /// @param alphaDenominator Denominator of `alpha` in the rebates function. - /// @param lambdaNumerator Numerator of `lambda` in the rebates function. - /// @param lambdaDenominator Denominator of `lambda` in the rebates function. - /// @return rewards Rewards owed to the staking pool. + /** + * @notice The exponential formula used to compute fee-based rewards for staking pools in a given epoch + * @dev This function does not perform bounds checking on the inputs, but the following conditions + * need to be true: + * 0 <= alphaNumerator / alphaDenominator <= 1 + * 0 < lambdaNumerator / lambdaDenominator + * The exponential rebates function has the form: + * `(1 - alpha * exp ^ (-lambda * stake / fees)) * fees` + * @param fees Fees generated by indexer in the staking pool + * @param stake Stake attributed to the indexer in the staking pool + * @param alphaNumerator Numerator of `alpha` in the rebates function + * @param alphaDenominator Denominator of `alpha` in the rebates function + * @param lambdaNumerator Numerator of `lambda` in the rebates function + * @param lambdaDenominator Denominator of `lambda` in the rebates function + * @return rewards Rewards owed to the staking pool + */ function exponentialRebates( uint256 fees, uint256 stake, diff --git a/packages/horizon/contracts/staking/utilities/Managed.sol b/packages/horizon/contracts/staking/utilities/Managed.sol index b2e36056f..88d2fb7c1 100644 --- a/packages/horizon/contracts/staking/utilities/Managed.sol +++ b/packages/horizon/contracts/staking/utilities/Managed.sol @@ -8,8 +8,9 @@ import { GraphDirectory } from "../../utilities/GraphDirectory.sol"; /** * @title Graph Managed contract - * @dev The Managed contract provides an interface to interact with the Controller. - * For Graph Horizon this contract is mostly a shell that uses {GraphDirectory}, however since the {HorizonStaking} + * @author Edge & Node + * @notice The Managed contract provides an interface to interact with the Controller + * @dev For Graph Horizon this contract is mostly a shell that uses {GraphDirectory}, however since the {HorizonStaking} * contract uses it we need to preserve the storage layout. * Inspired by Livepeer: https://github.com/livepeer/protocol/blob/streamflow/contracts/Controller.sol * @custom:security-contact Please email security+contracts@thegraph.com if you find any @@ -59,8 +60,8 @@ abstract contract Managed is GraphDirectory { } /** - * @dev Initialize the contract - * @param controller_ The address of the Graph controller contract. + * @notice Initialize the contract + * @param controller_ The address of the Graph controller contract */ constructor(address controller_) GraphDirectory(controller_) {} } diff --git a/packages/horizon/contracts/utilities/Authorizable.sol b/packages/horizon/contracts/utilities/Authorizable.sol index 3c77e951e..6af2e677f 100644 --- a/packages/horizon/contracts/utilities/Authorizable.sol +++ b/packages/horizon/contracts/utilities/Authorizable.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + import { IAuthorizable } from "@graphprotocol/interfaces/contracts/horizon/IAuthorizable.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -8,6 +11,7 @@ import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/Mes /** * @title Authorizable contract + * @author Edge & Node * @dev Implements the {IAuthorizable} interface. * @notice A mechanism to authorize signers to sign messages on behalf of an authorizer. * Signers cannot be reused for different authorizers. diff --git a/packages/horizon/contracts/utilities/GraphDirectory.sol b/packages/horizon/contracts/utilities/GraphDirectory.sol index 469410c8b..6e657c6d7 100644 --- a/packages/horizon/contracts/utilities/GraphDirectory.sol +++ b/packages/horizon/contracts/utilities/GraphDirectory.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol"; @@ -17,6 +17,7 @@ import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curatio /** * @title GraphDirectory contract + * @author Edge & Node * @notice This contract is meant to be inherited by other contracts that * need to keep track of the addresses in Graph Horizon contracts. * It fetches the addresses from the Controller supplied during construction, diff --git a/packages/horizon/test/unit/utilities/GraphDirectoryImplementation.sol b/packages/horizon/test/unit/utilities/GraphDirectoryImplementation.sol index f62576084..4a88bf0cd 100644 --- a/packages/horizon/test/unit/utilities/GraphDirectoryImplementation.sol +++ b/packages/horizon/test/unit/utilities/GraphDirectoryImplementation.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol"; import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; import { IPaymentsEscrow } from "@graphprotocol/interfaces/contracts/horizon/IPaymentsEscrow.sol"; diff --git a/packages/interfaces/README.md b/packages/interfaces/README.md index ff0fe932d..a93f9d544 100644 --- a/packages/interfaces/README.md +++ b/packages/interfaces/README.md @@ -4,7 +4,7 @@ Contract interfaces and types for The Graph protocol. ## Overview -This package contains contract interfaces and types used in dependent packages, which makes building systems that interact with The Graph contracts simpler, as the implementation information is not included. +This package contains contract interfaces and types used in dependent packages. ## Installation @@ -12,6 +12,15 @@ This package contains contract interfaces and types used in dependent packages, pnpm add @graphprotocol/interfaces ``` +## Compiler Version Strategy + +Interface pragma statements use open-ended caret ranges (e.g., `^0.8.22`) rather than exact versions to maximize compatibility with future packages and compiler versions: + +- **Dual-version interfaces** (`^0.7.6 || ^0.8.0` or `^0.7.3 || ^0.8.0`): Maintain compatibility with both Solidity 0.7.x and 0.8.x implementations +- **Modern interfaces** (`^0.8.22`): Allow the use of Solidity 0.8-specific features like custom errors, named mapping parameters, and file-level events + +This approach allows consuming projects to use any compatible compiler version within the specified range. + ## Usage ### Contract interfaces @@ -46,6 +55,16 @@ import { } from '@graphprotocol/interfaces' ``` +#### Available Type Formats + +This package generates TypeScript types in multiple formats to support different environments: + +- **ethers v6** (default): `dist/types/` - Modern ethers.js types (imported from package root) +- **ethers v5**: `dist/types-v5/` - Legacy ethers.js support for projects still using ethers v5 +- **Wagmi**: `dist/wagmi/` - React hooks for the wagmi library + +Import the appropriate version based on your project's dependencies. + ### TypeScript library Additionally, the package exposes a few helper functions to facilitate the creation of fully typed ethers v6 contracts: @@ -57,10 +76,7 @@ Additionally, the package exposes a few helper functions to facilitate the creat | `getAbi` | Gets the ABI for a given contract name. | ```ts -import { - getInterface, - SubgraphService -} from '@graphprotocol/interfaces +import { getInterface, SubgraphService } from '@graphprotocol/interfaces' const subgraphService = new ethers.Contract('0x12...90', getInterface('SubgraphService')) as SubgraphService ``` diff --git a/packages/interfaces/contracts/contracts/arbitrum/IArbToken.sol b/packages/interfaces/contracts/contracts/arbitrum/IArbToken.sol index 35fd7beb3..2a44467cf 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/IArbToken.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/IArbToken.sol @@ -27,20 +27,30 @@ * @title Minimum expected interface for L2 token that interacts with the L2 token bridge (this is the interface necessary * for a custom token that interacts with the bridge, see TestArbCustomToken.sol for an example implementation). */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Arbitrum Token Interface + * @author Edge & Node + * @notice Interface for tokens that can be minted and burned on Arbitrum L2 + */ interface IArbToken { /** * @notice should increase token supply by amount, and should (probably) only be callable by the L1 bridge. + * @param account Account to mint tokens to + * @param amount Amount of tokens to mint */ function bridgeMint(address account, uint256 amount) external; /** * @notice should decrease token supply by amount, and should (probably) only be callable by the L1 bridge. + * @param account Account to burn tokens from + * @param amount Amount of tokens to burn */ function bridgeBurn(address account, uint256 amount) external; /** + * @notice Get the L1 token address * @return address of layer 1 token */ function l1Address() external view returns (address); diff --git a/packages/interfaces/contracts/contracts/arbitrum/IBridge.sol b/packages/interfaces/contracts/contracts/arbitrum/IBridge.sol index 791c2b1a1..7a5b980aa 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/IBridge.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/IBridge.sol @@ -23,9 +23,26 @@ * */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + +/** + * @title Bridge Interface + * @author Edge & Node + * @notice Interface for the Arbitrum Bridge contract + */ interface IBridge { + /** + * @notice Emitted when a message is delivered to the inbox + * @param messageIndex Index of the message + * @param beforeInboxAcc Inbox accumulator before this message + * @param inbox Address of the inbox + * @param kind Type of the message + * @param sender Address that sent the message + * @param messageDataHash Hash of the message data + */ event MessageDelivered( uint256 indexed messageIndex, bytes32 indexed beforeInboxAcc, @@ -35,38 +52,102 @@ interface IBridge { bytes32 messageDataHash ); + /** + * @notice Emitted when a bridge call is triggered + * @param outbox Address of the outbox + * @param destAddr Destination address for the call + * @param amount ETH amount sent with the call + * @param data Calldata for the function call + */ event BridgeCallTriggered(address indexed outbox, address indexed destAddr, uint256 amount, bytes data); + /** + * @notice Emitted when an inbox is enabled or disabled + * @param inbox Address of the inbox + * @param enabled Whether the inbox is enabled + */ event InboxToggle(address indexed inbox, bool enabled); + /** + * @notice Emitted when an outbox is enabled or disabled + * @param outbox Address of the outbox + * @param enabled Whether the outbox is enabled + */ event OutboxToggle(address indexed outbox, bool enabled); + /** + * @notice Deliver a message to the inbox + * @param kind Type of the message + * @param sender Address that is sending the message + * @param messageDataHash keccak256 hash of the message data + * @return The message index + */ function deliverMessageToInbox( uint8 kind, address sender, bytes32 messageDataHash ) external payable returns (uint256); + /** + * @notice Execute a call from L2 to L1 + * @param destAddr Contract to call + * @param amount ETH value to send + * @param data Calldata for the function call + * @return success True if the call was successful, false otherwise + * @return returnData Return data from the call + */ function executeCall( address destAddr, uint256 amount, bytes calldata data ) external returns (bool success, bytes memory returnData); - // These are only callable by the admin + /** + * @notice Set the address of an inbox + * @param inbox Address of the inbox + * @param enabled Whether to enable the inbox + */ function setInbox(address inbox, bool enabled) external; + /** + * @notice Set the address of an outbox + * @param inbox Address of the outbox + * @param enabled Whether to enable the outbox + */ function setOutbox(address inbox, bool enabled) external; // View functions + /** + * @notice Get the active outbox address + * @return The active outbox address + */ function activeOutbox() external view returns (address); + /** + * @notice Check if an address is an allowed inbox + * @param inbox Address to check + * @return True if the address is an allowed inbox, false otherwise + */ function allowedInboxes(address inbox) external view returns (bool); + /** + * @notice Check if an address is an allowed outbox + * @param outbox Address to check + * @return True if the address is an allowed outbox, false otherwise + */ function allowedOutboxes(address outbox) external view returns (bool); + /** + * @notice Get the inbox accumulator at a specific index + * @param index Index to query + * @return The inbox accumulator at the given index + */ function inboxAccs(uint256 index) external view returns (bytes32); + /** + * @notice Get the count of messages in the inbox + * @return Number of messages in the inbox + */ function messageCount() external view returns (uint256); } diff --git a/packages/interfaces/contracts/contracts/arbitrum/IInbox.sol b/packages/interfaces/contracts/contracts/arbitrum/IInbox.sol index f216128cf..c8931522b 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/IInbox.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/IInbox.sol @@ -23,14 +23,34 @@ * */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; import { IBridge } from "./IBridge.sol"; import { IMessageProvider } from "./IMessageProvider.sol"; +/** + * @title Inbox Interface + * @author Edge & Node + * @notice Interface for the Arbitrum Inbox contract + */ interface IInbox is IMessageProvider { + /** + * @notice Send a message to L2 + * @param messageData Encoded data to send in the message + * @return Message number returned by the inbox + */ function sendL2Message(bytes calldata messageData) external returns (uint256); + /** + * @notice Send an unsigned transaction to L2 + * @param maxGas Maximum gas for the L2 transaction + * @param gasPriceBid Gas price bid for the L2 transaction + * @param nonce Nonce for the transaction + * @param destAddr Destination address on L2 + * @param amount Amount of ETH to send + * @param data Transaction data + * @return Message number returned by the inbox + */ function sendUnsignedTransaction( uint256 maxGas, uint256 gasPriceBid, @@ -40,6 +60,15 @@ interface IInbox is IMessageProvider { bytes calldata data ) external returns (uint256); + /** + * @notice Send a contract transaction to L2 + * @param maxGas Maximum gas for the L2 transaction + * @param gasPriceBid Gas price bid for the L2 transaction + * @param destAddr Destination address on L2 + * @param amount Amount of ETH to send + * @param data Transaction data + * @return Message number returned by the inbox + */ function sendContractTransaction( uint256 maxGas, uint256 gasPriceBid, @@ -48,6 +77,15 @@ interface IInbox is IMessageProvider { bytes calldata data ) external returns (uint256); + /** + * @notice Send an L1-funded unsigned transaction to L2 + * @param maxGas Maximum gas for the L2 transaction + * @param gasPriceBid Gas price bid for the L2 transaction + * @param nonce Nonce for the transaction + * @param destAddr Destination address on L2 + * @param data Transaction data + * @return Message number returned by the inbox + */ function sendL1FundedUnsignedTransaction( uint256 maxGas, uint256 gasPriceBid, @@ -56,6 +94,14 @@ interface IInbox is IMessageProvider { bytes calldata data ) external payable returns (uint256); + /** + * @notice Send an L1-funded contract transaction to L2 + * @param maxGas Maximum gas for the L2 transaction + * @param gasPriceBid Gas price bid for the L2 transaction + * @param destAddr Destination address on L2 + * @param data Transaction data + * @return Message number returned by the inbox + */ function sendL1FundedContractTransaction( uint256 maxGas, uint256 gasPriceBid, @@ -63,6 +109,18 @@ interface IInbox is IMessageProvider { bytes calldata data ) external payable returns (uint256); + /** + * @notice Create a retryable ticket for an L2 transaction + * @param destAddr Destination address on L2 + * @param arbTxCallValue Call value for the L2 transaction + * @param maxSubmissionCost Maximum cost for submitting the ticket + * @param submissionRefundAddress Address to refund submission cost to + * @param valueRefundAddress Address to refund excess value to + * @param maxGas Maximum gas for the L2 transaction + * @param gasPriceBid Gas price bid for the L2 transaction + * @param data Transaction data + * @return Message number returned by the inbox + */ function createRetryableTicket( address destAddr, uint256 arbTxCallValue, @@ -74,15 +132,36 @@ interface IInbox is IMessageProvider { bytes calldata data ) external payable returns (uint256); + /** + * @notice Deposit ETH to L2 + * @param maxSubmissionCost Maximum cost for submitting the deposit + * @return Message number returned by the inbox + */ function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); + /** + * @notice Get the bridge contract + * @return The bridge contract address + */ function bridge() external view returns (IBridge); + /** + * @notice Pause the creation of retryable tickets + */ function pauseCreateRetryables() external; + /** + * @notice Unpause the creation of retryable tickets + */ function unpauseCreateRetryables() external; + /** + * @notice Start rewriting addresses + */ function startRewriteAddress() external; + /** + * @notice Stop rewriting addresses + */ function stopRewriteAddress() external; } diff --git a/packages/interfaces/contracts/contracts/arbitrum/IMessageProvider.sol b/packages/interfaces/contracts/contracts/arbitrum/IMessageProvider.sol index 50b674c70..ca603ac41 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/IMessageProvider.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/IMessageProvider.sol @@ -23,10 +23,24 @@ * */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Message Provider Interface + * @author Edge & Node + * @notice Interface for Arbitrum message providers + */ interface IMessageProvider { + /** + * @notice Emitted when a message is delivered to the inbox + * @param messageNum Message number + * @param data Message data + */ event InboxMessageDelivered(uint256 indexed messageNum, bytes data); + /** + * @notice Emitted when a message is delivered from origin + * @param messageNum Message number + */ event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); } diff --git a/packages/interfaces/contracts/contracts/arbitrum/IOutbox.sol b/packages/interfaces/contracts/contracts/arbitrum/IOutbox.sol index 49617a976..732a99b62 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/IOutbox.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/IOutbox.sol @@ -23,15 +23,38 @@ * */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + +/** + * @title Arbitrum Outbox Interface + * @author Edge & Node + * @notice Interface for the Arbitrum outbox contract + */ interface IOutbox { + /** + * @notice Emitted when an outbox entry is created + * @param batchNum Batch number + * @param outboxEntryIndex Index of the outbox entry + * @param outputRoot Output root hash + * @param numInBatch Number of messages in the batch + */ event OutboxEntryCreated( uint256 indexed batchNum, uint256 outboxEntryIndex, bytes32 outputRoot, uint256 numInBatch ); + + /** + * @notice Emitted when an outbox transaction is executed + * @param destAddr Destination address + * @param l2Sender L2 sender address + * @param outboxEntryIndex Index of the outbox entry + * @param transactionIndex Index of the transaction + */ event OutBoxTransactionExecuted( address indexed destAddr, address indexed l2Sender, @@ -39,19 +62,53 @@ interface IOutbox { uint256 transactionIndex ); + /** + * @notice Get the L2 to L1 sender address + * @return The sender address + */ function l2ToL1Sender() external view returns (address); + /** + * @notice Get the L2 to L1 block number + * @return The block number + */ function l2ToL1Block() external view returns (uint256); + /** + * @notice Get the L2 to L1 Ethereum block number + * @return The Ethereum block number + */ function l2ToL1EthBlock() external view returns (uint256); + /** + * @notice Get the L2 to L1 timestamp + * @return The timestamp + */ function l2ToL1Timestamp() external view returns (uint256); + /** + * @notice Get the L2 to L1 batch number + * @return The batch number + */ function l2ToL1BatchNum() external view returns (uint256); + /** + * @notice Get the L2 to L1 output ID + * @return The output ID + */ function l2ToL1OutputId() external view returns (bytes32); + /** + * @notice Process outgoing messages + * @param sendsData Encoded message data + * @param sendLengths Array of message lengths + */ function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) external; + /** + * @notice Check if an outbox entry exists + * @param batchNum Batch number to check + * @return True if the entry exists + */ function outboxEntryExists(uint256 batchNum) external view returns (bool); } diff --git a/packages/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol b/packages/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol index 3b12e578e..aa3a018ac 100644 --- a/packages/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol +++ b/packages/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol @@ -23,8 +23,13 @@ * */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.3 || ^0.8.0; +/** + * @title Token Gateway Interface + * @author Edge & Node + * @notice Interface for token gateways that handle cross-chain token transfers + */ interface ITokenGateway { /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated // event OutboundTransferInitiated( @@ -46,15 +51,33 @@ interface ITokenGateway { // bytes _data // ); + /** + * @notice Transfer tokens from L1 to L2 or L2 to L1 + * @param token Address of the token being transferred + * @param to Recipient address on the destination chain + * @param amount Amount of tokens to transfer + * @param maxGas Maximum gas for the transaction + * @param gasPriceBid Gas price bid for the transaction + * @param data Additional data for the transfer + * @return Transaction data + */ function outboundTransfer( address token, address to, - uint256 amunt, - uint256 maxas, - uint256 gasPiceBid, + uint256 amount, + uint256 maxGas, + uint256 gasPriceBid, bytes calldata data ) external payable returns (bytes memory); + /** + * @notice Finalize an inbound token transfer + * @param token Address of the token being transferred + * @param from Sender address on the source chain + * @param to Recipient address on the destination chain + * @param amount Amount of tokens being transferred + * @param data Additional data for the transfer + */ function finalizeInboundTransfer( address token, address from, diff --git a/packages/interfaces/contracts/contracts/base/IMulticall.sol b/packages/interfaces/contracts/contracts/base/IMulticall.sol index 10f7fa469..a30be5e7e 100644 --- a/packages/interfaces/contracts/contracts/base/IMulticall.sol +++ b/packages/interfaces/contracts/contracts/base/IMulticall.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; /** * @title Multicall interface + * @author Edge & Node * @notice Enables calling multiple methods in a single call to the contract */ interface IMulticall { diff --git a/packages/interfaces/contracts/contracts/curation/ICuration.sol b/packages/interfaces/contracts/contracts/curation/ICuration.sol index cb6271991..8ca4d12d8 100644 --- a/packages/interfaces/contracts/contracts/curation/ICuration.sol +++ b/packages/interfaces/contracts/contracts/curation/ICuration.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; /** * @title Curation Interface - * @dev Interface for the Curation contract (and L2Curation too) + * @author Edge & Node + * @notice Interface for the Curation contract (and L2Curation too) */ interface ICuration { // -- Configuration -- diff --git a/packages/interfaces/contracts/contracts/curation/IGraphCurationToken.sol b/packages/interfaces/contracts/contracts/curation/IGraphCurationToken.sol index 644593f54..c710d074d 100644 --- a/packages/interfaces/contracts/contracts/curation/IGraphCurationToken.sol +++ b/packages/interfaces/contracts/contracts/curation/IGraphCurationToken.sol @@ -1,13 +1,32 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +/** + * @title Graph Curation Token Interface + * @author Edge & Node + * @notice Interface for curation tokens that represent shares in subgraph curation pools + */ interface IGraphCurationToken is IERC20Upgradeable { + /** + * @notice Graph Curation Token Contract initializer. + * @param owner Address of the contract issuing this token + */ function initialize(address owner) external; + /** + * @notice Burn tokens from an address. + * @param account Address from where tokens will be burned + * @param amount Amount of tokens to burn + */ function burnFrom(address account, uint256 amount) external; + /** + * @notice Mint new tokens. + * @param to Address to send the newly minted tokens + * @param amount Amount of tokens to mint + */ function mint(address to, uint256 amount) external; } diff --git a/packages/interfaces/contracts/contracts/discovery/IGNS.sol b/packages/interfaces/contracts/contracts/discovery/IGNS.sol index 6c00ce694..d55c93521 100644 --- a/packages/interfaces/contracts/contracts/discovery/IGNS.sol +++ b/packages/interfaces/contracts/contracts/discovery/IGNS.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; /** * @title Interface for GNS + * @author Edge & Node + * @notice Interface for the Graph Name System (GNS) contract */ interface IGNS { // -- Pool -- @@ -12,6 +14,13 @@ interface IGNS { * @dev The SubgraphData struct holds information about subgraphs * and their signal; both nSignal (i.e. name signal at the GNS level) * and vSignal (i.e. version signal at the Curation contract level) + * @param vSignal The token of the subgraph-deployment bonding curve + * @param nSignal The token of the subgraph bonding curve + * @param curatorNSignal Mapping of curator addresses to their name signal amounts + * @param subgraphDeploymentID The deployment ID this subgraph points to + * @param __DEPRECATED_reserveRatio Deprecated reserve ratio field + * @param disabled Whether the subgraph is disabled/deprecated + * @param withdrawableGRT Amount of GRT available for withdrawal after deprecation */ struct SubgraphData { uint256 vSignal; // The token of the subgraph-deployment bonding curve @@ -26,23 +35,14 @@ interface IGNS { /** * @dev The LegacySubgraphKey struct holds the account and sequence ID * used to generate subgraph IDs in legacy subgraphs. + * @param account The account that created the legacy subgraph + * @param accountSeqID The sequence ID for the account's subgraphs */ struct LegacySubgraphKey { address account; uint256 accountSeqID; } - // -- Events -- - - /** - * @dev Emitted when a subgraph version is updated. - */ - event SubgraphVersionUpdated( - uint256 indexed subgraphID, - bytes32 indexed subgraphDeploymentID, - bytes32 versionMetadata - ); - // -- Configuration -- /** @@ -127,7 +127,7 @@ interface IGNS { function burnSignal(uint256 subgraphID, uint256 nSignal, uint256 tokensOutMin) external; /** - * @notice Move subgraph signal from sender to recipient + * @notice Move subgraph signal from sender to `recipient` * @param subgraphID Subgraph ID * @param recipient Address to send the signal to * @param amount The amount of nSignal to transfer @@ -169,7 +169,9 @@ interface IGNS { * @notice Calculate subgraph signal to be returned for an amount of tokens. * @param subgraphID Subgraph ID * @param tokensIn Tokens being exchanged for subgraph signal - * @return Amount of subgraph signal and curation tax + * @return Amount of subgraph signal that can be bought + * @return Amount of version signal that can be bought + * @return Amount of curation tax */ function tokensToNSignal(uint256 subgraphID, uint256 tokensIn) external view returns (uint256, uint256, uint256); @@ -178,6 +180,7 @@ interface IGNS { * @param subgraphID Subgraph ID * @param nSignalIn Subgraph signal being exchanged for tokens * @return Amount of tokens returned for an amount of subgraph signal + * @return Amount of version signal returned */ function nSignalToTokens(uint256 subgraphID, uint256 nSignalIn) external view returns (uint256, uint256); diff --git a/packages/interfaces/contracts/contracts/discovery/IServiceRegistry.sol b/packages/interfaces/contracts/contracts/discovery/IServiceRegistry.sol index b3f5377c0..e3ef02807 100644 --- a/packages/interfaces/contracts/contracts/discovery/IServiceRegistry.sol +++ b/packages/interfaces/contracts/contracts/discovery/IServiceRegistry.sol @@ -1,20 +1,53 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Service Registry Interface + * @author Edge & Node + * @notice Interface for the Service Registry contract that manages indexer service information + */ interface IServiceRegistry { + /** + * @dev Indexer service information + * @param url URL of the indexer service + * @param geohash Geohash of the indexer service location + */ struct IndexerService { string url; string geohash; } + /** + * @notice Register an indexer service + * @param url URL of the indexer service + * @param geohash Geohash of the indexer service location + */ function register(string calldata url, string calldata geohash) external; + /** + * @notice Register an indexer service + * @param indexer Address of the indexer + * @param url URL of the indexer service + * @param geohash Geohash of the indexer service location + */ function registerFor(address indexer, string calldata url, string calldata geohash) external; + /** + * @notice Unregister an indexer service + */ function unregister() external; + /** + * @notice Unregister an indexer service + * @param indexer Address of the indexer + */ function unregisterFor(address indexer) external; + /** + * @notice Return the registration status of an indexer service + * @param indexer Address of the indexer + * @return True if the indexer service is registered + */ function isRegistered(address indexer) external view returns (bool); } diff --git a/packages/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol b/packages/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol index 0f7d5a73d..c3be4216a 100644 --- a/packages/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol +++ b/packages/interfaces/contracts/contracts/discovery/ISubgraphNFT.sol @@ -1,25 +1,67 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +/** + * @title Subgraph NFT Interface + * @author Edge & Node + * @notice Interface for the Subgraph NFT contract that represents subgraph ownership + */ interface ISubgraphNFT is IERC721 { // -- Config -- + /** + * @notice Set the minter allowed to perform actions on the NFT + * @dev Minter can mint, burn and update the metadata + * @param minter Address of the allowed minter + */ function setMinter(address minter) external; + /** + * @notice Set the token descriptor contract + * @dev Token descriptor can be zero. If set, it must be a contract + * @param tokenDescriptor Address of the contract that creates the NFT token URI + */ function setTokenDescriptor(address tokenDescriptor) external; + /** + * @notice Set the base URI + * @dev Can be set to empty + * @param baseURI Base URI to use to build the token URI + */ function setBaseURI(string memory baseURI) external; // -- Actions -- + /** + * @notice Mint `tokenId` and transfers it to `to` + * @dev `tokenId` must not exist and `to` cannot be the zero address + * @param to Address receiving the minted NFT + * @param tokenId ID of the NFT + */ function mint(address to, uint256 tokenId) external; + /** + * @notice Burn `tokenId` + * @dev The approval is cleared when the token is burned + * @param tokenId ID of the NFT + */ function burn(uint256 tokenId) external; + /** + * @notice Set the metadata for a subgraph represented by `tokenId` + * @dev `tokenId` must exist + * @param tokenId ID of the NFT + * @param subgraphMetadata IPFS hash for the metadata + */ function setSubgraphMetadata(uint256 tokenId, bytes32 subgraphMetadata) external; + /** + * @notice Returns the Uniform Resource Identifier (URI) for `tokenId` token + * @param tokenId ID of the NFT + * @return The URI for the token + */ function tokenURI(uint256 tokenId) external view returns (string memory); } diff --git a/packages/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol b/packages/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol index 429e22898..c7ea19ad4 100644 --- a/packages/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol +++ b/packages/interfaces/contracts/contracts/discovery/ISubgraphNFTDescriptor.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; -/// @title Describes subgraph NFT tokens via URI +/** + * @title Describes subgraph NFT tokens via URI + * @author Edge & Node + * @notice Interface for describing subgraph NFT tokens via URI + */ interface ISubgraphNFTDescriptor { /// @notice Produces the URI describing a particular token ID for a Subgraph /// @dev Note this URI may be data: URI with the JSON contents directly inlined diff --git a/packages/interfaces/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol b/packages/interfaces/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol index c4bdf1ba8..6a8258e31 100644 --- a/packages/interfaces/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol +++ b/packages/interfaces/contracts/contracts/discovery/erc1056/IEthereumDIDRegistry.sol @@ -1,9 +1,26 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Ethereum DID Registry Interface + * @author Edge & Node + * @notice Interface for the Ethereum DID Registry contract + */ interface IEthereumDIDRegistry { + /** + * @notice Get the owner of an identity + * @param identity The identity address + * @return The address of the identity owner + */ function identityOwner(address identity) external view returns (address); + /** + * @notice Set an attribute for an identity + * @param identity The identity address + * @param name The attribute name + * @param value The attribute value + * @param validity The validity period in seconds + */ function setAttribute(address identity, bytes32 name, bytes calldata value, uint256 validity) external; } diff --git a/packages/interfaces/contracts/contracts/disputes/IDisputeManager.sol b/packages/interfaces/contracts/contracts/disputes/IDisputeManager.sol index eb86be5d0..e330f960d 100644 --- a/packages/interfaces/contracts/contracts/disputes/IDisputeManager.sol +++ b/packages/interfaces/contracts/contracts/disputes/IDisputeManager.sol @@ -1,17 +1,28 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.6.12 <0.8.0 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +/** + * @title Dispute Manager Interface + * @author Edge & Node + * @notice Interface for the Dispute Manager contract that handles indexing and query disputes + */ interface IDisputeManager { // -- Dispute -- + /** + * @dev Types of disputes that can be created + */ enum DisputeType { Null, IndexingDispute, QueryDispute } + /** + * @dev Status of a dispute + */ enum DisputeStatus { Null, Accepted, @@ -20,7 +31,15 @@ interface IDisputeManager { Pending } - // Disputes contain info necessary for the Arbitrator to verify and resolve + /** + * @dev Disputes contain info necessary for the Arbitrator to verify and resolve + * @param indexer Address of the indexer being disputed + * @param fisherman Address of the challenger creating the dispute + * @param deposit Amount of tokens staked as deposit + * @param relatedDisputeID ID of related dispute (for conflicting attestations) + * @param disputeType Type of dispute (Query or Indexing) + * @param status Current status of the dispute + */ struct Dispute { address indexer; address fisherman; @@ -32,14 +51,27 @@ interface IDisputeManager { // -- Attestation -- - // Receipt content sent from indexer in response to request + /** + * @dev Receipt content sent from indexer in response to request + * @param requestCID Content ID of the request + * @param responseCID Content ID of the response + * @param subgraphDeploymentID ID of the subgraph deployment + */ struct Receipt { bytes32 requestCID; bytes32 responseCID; bytes32 subgraphDeploymentID; } - // Attestation sent from indexer in response to a request + /** + * @dev Attestation sent from indexer in response to a request + * @param requestCID Content ID of the request + * @param responseCID Content ID of the response + * @param subgraphDeploymentID ID of the subgraph deployment + * @param r R component of the signature + * @param s S component of the signature + * @param v Recovery ID of the signature + */ struct Attestation { bytes32 requestCID; bytes32 responseCID; @@ -51,41 +83,121 @@ interface IDisputeManager { // -- Configuration -- + /** + * @dev Set the arbitrator address. + * @notice Update the arbitrator to `arbitrator` + * @param arbitrator The address of the arbitration contract or party + */ function setArbitrator(address arbitrator) external; + /** + * @dev Set the minimum deposit required to create a dispute. + * @notice Update the minimum deposit to `minimumDeposit` Graph Tokens + * @param minimumDeposit The minimum deposit in Graph Tokens + */ function setMinimumDeposit(uint256 minimumDeposit) external; + /** + * @dev Set the percent reward that the fisherman gets when slashing occurs. + * @notice Update the reward percentage to `percentage` + * @param percentage Reward as a percentage of indexer stake + */ function setFishermanRewardPercentage(uint32 percentage) external; + /** + * @notice Set the percentage used for slashing indexers. + * @param qryPercentage Percentage slashing for query disputes + * @param idxPercentage Percentage slashing for indexing disputes + */ function setSlashingPercentage(uint32 qryPercentage, uint32 idxPercentage) external; // -- Getters -- + /** + * @notice Check if a dispute has been created + * @param disputeID Dispute identifier + * @return True if the dispute exists + */ function isDisputeCreated(bytes32 disputeID) external view returns (bool); + /** + * @notice Encode a receipt into a hash for EIP-712 signature verification + * @param receipt The receipt to encode + * @return The encoded hash + */ function encodeHashReceipt(Receipt memory receipt) external view returns (bytes32); + /** + * @notice Check if two attestations are conflicting + * @param attestation1 First attestation + * @param attestation2 Second attestation + * @return True if attestations are conflicting + */ function areConflictingAttestations( Attestation memory attestation1, Attestation memory attestation2 ) external pure returns (bool); + /** + * @notice Get the indexer address from an attestation + * @param attestation The attestation to extract indexer from + * @return The indexer address + */ function getAttestationIndexer(Attestation memory attestation) external view returns (address); // -- Dispute -- + /** + * @notice Create a query dispute for the arbitrator to resolve. + * This function is called by a fisherman that will need to `deposit` at + * least `minimumDeposit` GRT tokens. + * @param attestationData Attestation bytes submitted by the fisherman + * @param deposit Amount of tokens staked as deposit + * @return The dispute ID + */ function createQueryDispute(bytes calldata attestationData, uint256 deposit) external returns (bytes32); + /** + * @notice Create query disputes for two conflicting attestations. + * A conflicting attestation is a proof presented by two different indexers + * where for the same request on a subgraph the response is different. + * For this type of dispute the submitter is not required to present a deposit + * as one of the attestation is considered to be right. + * Two linked disputes will be created and if the arbitrator resolve one, the other + * one will be automatically resolved. + * @param attestationData1 First attestation data submitted + * @param attestationData2 Second attestation data submitted + * @return First dispute ID + * @return Second dispute ID + */ function createQueryDisputeConflict( bytes calldata attestationData1, bytes calldata attestationData2 ) external returns (bytes32, bytes32); + /** + * @notice Create an indexing dispute + * @param allocationID Allocation ID being disputed + * @param deposit Deposit amount for the dispute + * @return The dispute ID + */ function createIndexingDispute(address allocationID, uint256 deposit) external returns (bytes32); + /** + * @notice Accept a dispute (arbitrator only) + * @param disputeID ID of the dispute to accept + */ function acceptDispute(bytes32 disputeID) external; + /** + * @notice Reject a dispute (arbitrator only) + * @param disputeID ID of the dispute to reject + */ function rejectDispute(bytes32 disputeID) external; + /** + * @notice Draw a dispute (arbitrator only) + * @param disputeID ID of the dispute to draw + */ function drawDispute(bytes32 disputeID) external; } diff --git a/packages/interfaces/contracts/contracts/epochs/IEpochManager.sol b/packages/interfaces/contracts/contracts/epochs/IEpochManager.sol index 06f3fd70a..8351e0933 100644 --- a/packages/interfaces/contracts/contracts/epochs/IEpochManager.sol +++ b/packages/interfaces/contracts/contracts/epochs/IEpochManager.sol @@ -1,31 +1,78 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Epoch Manager Interface + * @author Edge & Node + * @notice Interface for the Epoch Manager contract that handles protocol epochs + */ interface IEpochManager { // -- Configuration -- + /** + * @notice Set epoch length to `epochLength` blocks + * @param epochLength Epoch length in blocks + */ function setEpochLength(uint256 epochLength) external; // -- Epochs + /** + * @dev Run a new epoch, should be called once at the start of any epoch. + * @notice Perform state changes for the current epoch + */ function runEpoch() external; // -- Getters -- + /** + * @notice Check if the current epoch has been run + * @return True if current epoch has been run, false otherwise + */ function isCurrentEpochRun() external view returns (bool); + /** + * @notice Get the current block number + * @return Current block number + */ function blockNum() external view returns (uint256); - function blockHash(uint256 block) external view returns (bytes32); + /** + * @notice Get the hash of a specific block + * @param blockNumber Block number to get hash for + * @return Block hash + */ + function blockHash(uint256 blockNumber) external view returns (bytes32); + /** + * @notice Get the current epoch number + * @return Current epoch number + */ function currentEpoch() external view returns (uint256); + /** + * @notice Get the block number when the current epoch started + * @return Block number of current epoch start + */ function currentEpochBlock() external view returns (uint256); + /** + * @notice Get the number of blocks since the current epoch started + * @return Number of blocks since current epoch start + */ function currentEpochBlockSinceStart() external view returns (uint256); + /** + * @notice Get the number of epochs since a given epoch + * @param epoch Epoch to calculate from + * @return Number of epochs since the given epoch + */ function epochsSince(uint256 epoch) external view returns (uint256); + /** + * @notice Get the number of epochs since the last epoch length update + * @return Number of epochs since last update + */ function epochsSinceUpdate() external view returns (uint256); } diff --git a/packages/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol b/packages/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol index 86bc654a0..6e6958352 100644 --- a/packages/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol +++ b/packages/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol @@ -2,12 +2,18 @@ /** * @title Interface for contracts that can receive callhooks through the Arbitrum GRT bridge - * @dev Any contract that can receive a callhook on L2, sent through the bridge from L1, must + * @author Edge & Node + * @notice Any contract that can receive a callhook on L2, sent through the bridge from L1, must * be allowlisted by the governor, but also implement this interface that contains * the function that will actually be called by the L2GraphTokenGateway. */ -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.3 || ^0.8.0; +/** + * @title Callhook Receiver Interface + * @author Edge & Node + * @notice Interface for contracts that can receive tokens with callhook from the bridge + */ interface ICallhookReceiver { /** * @notice Receive tokens with a callhook from the bridge diff --git a/packages/interfaces/contracts/contracts/governance/IController.sol b/packages/interfaces/contracts/contracts/governance/IController.sol index 2f6d3b6b7..6d786e165 100644 --- a/packages/interfaces/contracts/contracts/governance/IController.sol +++ b/packages/interfaces/contracts/contracts/governance/IController.sol @@ -1,29 +1,79 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Controller Interface + * @author Edge & Node + * @notice Interface for the Controller contract that manages protocol governance and contract registry + */ interface IController { + /** + * @notice Return the governor address + * @return The governor address + */ function getGovernor() external view returns (address); // -- Registry -- + /** + * @notice Register contract id and mapped address + * @param id Contract id (keccak256 hash of contract name) + * @param contractAddress Contract address + */ function setContractProxy(bytes32 id, address contractAddress) external; + /** + * @notice Unregister a contract address + * @param id Contract id (keccak256 hash of contract name) + */ function unsetContractProxy(bytes32 id) external; + /** + * @notice Update contract's controller + * @param id Contract id (keccak256 hash of contract name) + * @param controller Controller address + */ function updateController(bytes32 id, address controller) external; + /** + * @notice Get contract proxy address by its id + * @param id Contract id + * @return Address of the proxy contract for the provided id + */ function getContractProxy(bytes32 id) external view returns (address); // -- Pausing -- - function setPartialPaused(bool partialPaused) external; + /** + * @notice Change the partial paused state of the contract + * Partial pause is intended as a partial pause of the protocol + * @param partialPause True if the contracts should be (partially) paused, false otherwise + */ + function setPartialPaused(bool partialPause) external; - function setPaused(bool paused) external; + /** + * @notice Change the paused state of the contract + * Full pause most of protocol functions + * @param pause True if the contracts should be paused, false otherwise + */ + function setPaused(bool pause) external; + /** + * @notice Change the Pause Guardian + * @param newPauseGuardian The address of the new Pause Guardian + */ function setPauseGuardian(address newPauseGuardian) external; + /** + * @notice Return whether the protocol is paused + * @return True if the protocol is paused + */ function paused() external view returns (bool); + /** + * @notice Return whether the protocol is partially paused + * @return True if the protocol is partially paused + */ function partialPaused() external view returns (bool); } diff --git a/packages/interfaces/contracts/contracts/governance/IGoverned.sol b/packages/interfaces/contracts/contracts/governance/IGoverned.sol index 200f74ecf..c7688a6cd 100644 --- a/packages/interfaces/contracts/contracts/governance/IGoverned.sol +++ b/packages/interfaces/contracts/contracts/governance/IGoverned.sol @@ -1,15 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; /** * @title IGoverned - * @dev Interface for the Governed contract. + * @author Edge & Node + * @notice Interface for governed contracts */ interface IGoverned { // -- State getters -- + /** + * @notice Get the current governor address + * @return The address of the current governor + */ function governor() external view returns (address); + /** + * @notice Get the pending governor address + * @return The address of the pending governor + */ function pendingGovernor() external view returns (address); // -- External functions -- @@ -27,7 +36,17 @@ interface IGoverned { // -- Events -- + /** + * @notice Emitted when a new pending governor is set + * @param from The address of the current governor + * @param to The address of the new pending governor + */ event NewPendingOwnership(address indexed from, address indexed to); + /** + * @notice Emitted when governance is transferred to a new governor + * @param from The address of the previous governor + * @param to The address of the new governor + */ event NewOwnership(address indexed from, address indexed to); } diff --git a/packages/interfaces/contracts/contracts/governance/IManaged.sol b/packages/interfaces/contracts/contracts/governance/IManaged.sol index 9bfe57083..d45dacaee 100644 --- a/packages/interfaces/contracts/contracts/governance/IManaged.sol +++ b/packages/interfaces/contracts/contracts/governance/IManaged.sol @@ -1,20 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; import { IController } from "./IController.sol"; /** * @title Managed Interface - * @dev Interface for contracts that can be managed by a controller. + * @author Edge & Node + * @notice Interface for contracts that can be managed by a controller. */ interface IManaged { /** * @notice Set the controller that manages this contract * @dev Only the current controller can set a new controller - * @param controller Address of the new controller + * @param newController Address of the new controller */ - function setController(address controller) external; + function setController(address newController) external; /** * @notice Sync protocol contract addresses from the Controller registry diff --git a/packages/interfaces/contracts/contracts/l2/curation/IL2Curation.sol b/packages/interfaces/contracts/contracts/l2/curation/IL2Curation.sol index 18f6882cc..889f7a852 100644 --- a/packages/interfaces/contracts/contracts/l2/curation/IL2Curation.sol +++ b/packages/interfaces/contracts/contracts/l2/curation/IL2Curation.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; /** * @title Interface of the L2 Curation contract. + * @author Edge & Node + * @notice Interface for the L2 Curation contract that handles curation on Layer 2 */ interface IL2Curation { /** diff --git a/packages/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol b/packages/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol index 12a1aeec9..5b60fc1a7 100644 --- a/packages/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol +++ b/packages/interfaces/contracts/contracts/l2/discovery/IL2GNS.sol @@ -1,13 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; /** * @title Interface for the L2GNS contract. + * @author Edge & Node + * @notice Interface for the L2 Graph Name System (GNS) contract */ interface IL2GNS is ICallhookReceiver { + /** + * @dev Message codes for L1 to L2 communication + * @param RECEIVE_SUBGRAPH_CODE Code for receiving subgraph transfers + * @param RECEIVE_CURATOR_BALANCE_CODE Code for receiving curator balance transfers + */ enum L1MessageCodes { RECEIVE_SUBGRAPH_CODE, RECEIVE_CURATOR_BALANCE_CODE @@ -16,6 +23,10 @@ interface IL2GNS is ICallhookReceiver { /** * @dev The SubgraphL2TransferData struct holds information * about a subgraph related to its transfer from L1 to L2. + * @param tokens GRT that will be sent to L2 to mint signal + * @param curatorBalanceClaimed True for curators whose balance has been claimed in L2 + * @param l2Done Transfer finished on L2 side + * @param subgraphReceivedOnL2BlockNumber Block number when the subgraph was received on L2 */ struct SubgraphL2TransferData { uint256 tokens; // GRT that will be sent to L2 to mint signal diff --git a/packages/interfaces/contracts/contracts/l2/gateway/IL2GraphTokenGateway.sol b/packages/interfaces/contracts/contracts/l2/gateway/IL2GraphTokenGateway.sol index b1e088212..b6e6df745 100644 --- a/packages/interfaces/contracts/contracts/l2/gateway/IL2GraphTokenGateway.sol +++ b/packages/interfaces/contracts/contracts/l2/gateway/IL2GraphTokenGateway.sol @@ -1,7 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + +/** + * @title IL2GraphTokenGateway + * @author Edge & Node + * @notice Interface for the L2 Graph Token Gateway contract that handles token bridging on L2 + */ interface IL2GraphTokenGateway { // Structs struct OutboundCalldata { @@ -10,7 +18,24 @@ interface IL2GraphTokenGateway { } // Events + /** + * @notice Emitted when a deposit from L1 is finalized on L2 + * @param l1Token The L1 token address + * @param from The sender address on L1 + * @param to The recipient address on L2 + * @param amount The amount of tokens deposited + */ event DepositFinalized(address indexed l1Token, address indexed from, address indexed to, uint256 amount); + + /** + * @notice Emitted when a withdrawal from L2 to L1 is initiated + * @param l1Token The L1 token address + * @param from The sender address on L2 + * @param to The recipient address on L1 + * @param l2ToL1Id The L2 to L1 message ID + * @param exitNum The exit number + * @param amount The amount of tokens withdrawn + */ event WithdrawalInitiated( address l1Token, address indexed from, @@ -19,19 +44,58 @@ interface IL2GraphTokenGateway { uint256 exitNum, uint256 amount ); + + /** + * @notice Emitted when the L2 router address is set + * @param l2Router The new L2 router address + */ event L2RouterSet(address l2Router); + + /** + * @notice Emitted when the L1 token address is set + * @param l1GRT The L1 GRT token address + */ event L1TokenAddressSet(address l1GRT); + + /** + * @notice Emitted when the L1 counterpart address is set + * @param l1Counterpart The L1 counterpart gateway address + */ event L1CounterpartAddressSet(address l1Counterpart); // Functions + /** + * @notice Initialize the gateway contract + * @param controller The controller contract address + */ function initialize(address controller) external; + /** + * @notice Set the L2 router address + * @param l2Router The L2 router contract address + */ function setL2Router(address l2Router) external; + /** + * @notice Set the L1 token address + * @param l1GRT The L1 GRT token contract address + */ function setL1TokenAddress(address l1GRT) external; + /** + * @notice Set the L1 counterpart gateway address + * @param l1Counterpart The L1 counterpart gateway contract address + */ function setL1CounterpartAddress(address l1Counterpart) external; + /** + * @notice Transfer tokens from L2 to L1 + * @param l1Token The L1 token address + * @param to The recipient address on L1 + * @param amount The amount of tokens to transfer + * @param data Additional data for the transfer + * @return The encoded outbound transfer data + */ function outboundTransfer( address l1Token, address to, @@ -39,6 +103,14 @@ interface IL2GraphTokenGateway { bytes calldata data ) external returns (bytes memory); + /** + * @notice Finalize an inbound transfer from L1 to L2 + * @param l1Token The L1 token address + * @param from The sender address on L1 + * @param to The recipient address on L2 + * @param amount The amount of tokens to transfer + * @param data Additional data for the transfer + */ function finalizeInboundTransfer( address l1Token, address from, @@ -47,6 +119,16 @@ interface IL2GraphTokenGateway { bytes calldata data ) external payable; + /** + * @notice Transfer tokens from L2 to L1 (overloaded version with unused parameters) + * @param l1Token The L1 token address + * @param to The recipient address on L1 + * @param amount The amount of tokens to transfer + * @param unused1 Unused parameter for compatibility + * @param unused2 Unused parameter for compatibility + * @param data Additional data for the transfer + * @return The encoded outbound transfer data + */ function outboundTransfer( address l1Token, address to, @@ -56,8 +138,22 @@ interface IL2GraphTokenGateway { bytes calldata data ) external payable returns (bytes memory); + /** + * @notice Calculate the L2 token address for a given L1 token + * @param l1ERC20 The L1 token address + * @return The corresponding L2 token address + */ function calculateL2TokenAddress(address l1ERC20) external view returns (address); + /** + * @notice Get the encoded calldata for an outbound transfer + * @param token The token address + * @param from The sender address + * @param to The recipient address + * @param amount The amount of tokens + * @param data Additional transfer data + * @return The encoded calldata + */ function getOutboundCalldata( address token, address from, diff --git a/packages/interfaces/contracts/contracts/l2/staking/IL2Staking.sol b/packages/interfaces/contracts/contracts/l2/staking/IL2Staking.sol index 0b1718571..4fa87e294 100644 --- a/packages/interfaces/contracts/contracts/l2/staking/IL2Staking.sol +++ b/packages/interfaces/contracts/contracts/l2/staking/IL2Staking.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; import { IStaking } from "../../staking/IStaking.sol"; @@ -9,6 +9,7 @@ import { IL2StakingTypes } from "./IL2StakingTypes.sol"; /** * @title Interface for the L2 Staking contract + * @author Edge & Node * @notice This is the interface that should be used when interacting with the L2 Staking contract. * It extends the IStaking interface with the functions that are specific to L2, adding the callhook receiver * to receive transferred stake and delegation from L1. diff --git a/packages/interfaces/contracts/contracts/l2/staking/IL2StakingBase.sol b/packages/interfaces/contracts/contracts/l2/staking/IL2StakingBase.sol index f5c33c2d0..0eaff2b90 100644 --- a/packages/interfaces/contracts/contracts/l2/staking/IL2StakingBase.sol +++ b/packages/interfaces/contracts/contracts/l2/staking/IL2StakingBase.sol @@ -1,14 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol"; /** * @title Base interface for the L2Staking contract. + * @author Edge & Node * @notice This interface is used to define the callhook receiver interface that is implemented by L2Staking. * @dev Note it includes only the L2-specific functionality, not the full IStaking interface. */ interface IL2StakingBase is ICallhookReceiver { + /** + * @notice Emitted when transferred delegation is returned to a delegator + * @param indexer Address of the indexer + * @param delegator Address of the delegator + * @param amount Amount of delegation returned + */ event TransferredDelegationReturnedToDelegator(address indexed indexer, address indexed delegator, uint256 amount); } diff --git a/packages/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol b/packages/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol index 500694e89..672f92513 100644 --- a/packages/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol +++ b/packages/interfaces/contracts/contracts/l2/staking/IL2StakingTypes.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title IL2StakingTypes + * @author Edge & Node + * @notice Interface defining types and enums used by L2 staking contracts + */ interface IL2StakingTypes { /// @dev Message codes for the L1 -> L2 bridge callhook enum L1MessageCodes { diff --git a/packages/interfaces/contracts/contracts/l2/token/IL2GraphToken.sol b/packages/interfaces/contracts/contracts/l2/token/IL2GraphToken.sol index 92b12946d..b12788a5d 100644 --- a/packages/interfaces/contracts/contracts/l2/token/IL2GraphToken.sol +++ b/packages/interfaces/contracts/contracts/l2/token/IL2GraphToken.sol @@ -1,20 +1,63 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IGraphToken } from "../../token/IGraphToken.sol"; +/** + * @title IL2GraphToken + * @author Edge & Node + * @notice Interface for the L2 Graph Token contract that extends IGraphToken with L2-specific functionality + */ interface IL2GraphToken is IGraphToken { // Events + /** + * @notice Emitted when tokens are minted through the bridge + * @param account The account that received the minted tokens + * @param amount The amount of tokens minted + */ event BridgeMinted(address indexed account, uint256 amount); + + /** + * @notice Emitted when tokens are burned through the bridge + * @param account The account from which tokens were burned + * @param amount The amount of tokens burned + */ event BridgeBurned(address indexed account, uint256 amount); + + /** + * @notice Emitted when the gateway address is set + * @param gateway The new gateway address + */ event GatewaySet(address gateway); + + /** + * @notice Emitted when the L1 address is set + * @param l1Address The new L1 address + */ event L1AddressSet(address l1Address); // Public state variables (view functions) + /** + * @notice Get the gateway contract address + * @return The address of the gateway contract + */ function gateway() external view returns (address); + + /** + * @notice Get the L1 contract address + * @return The address of the L1 contract + */ function l1Address() external view returns (address); // Functions + + /** + * @notice Initialize the L2 token contract + * @param owner The owner address for the contract + */ function initialize(address owner) external; /** diff --git a/packages/interfaces/contracts/contracts/rewards/ILegacyRewardsManager.sol b/packages/interfaces/contracts/contracts/rewards/ILegacyRewardsManager.sol index 7ea628e20..0414b55cb 100644 --- a/packages/interfaces/contracts/contracts/rewards/ILegacyRewardsManager.sol +++ b/packages/interfaces/contracts/contracts/rewards/ILegacyRewardsManager.sol @@ -1,7 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title ILegacyRewardsManager + * @author Edge & Node + * @notice Interface for the legacy rewards manager contract + */ interface ILegacyRewardsManager { + /** + * @notice Get the accumulated rewards for a given allocation + * @param allocationID The allocation identifier + * @return The amount of accumulated rewards + */ function getRewards(address allocationID) external view returns (uint256); } diff --git a/packages/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol b/packages/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol index 0f1ed9d8f..34e398b2a 100644 --- a/packages/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol +++ b/packages/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol @@ -1,10 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Rewards Issuer Interface + * @author Edge & Node + * @notice Interface for contracts that issue rewards based on allocation data + */ interface IRewardsIssuer { /** - * @dev Get allocation data to calculate rewards issuance + * @notice Get allocation data to calculate rewards issuance * * @param allocationId The allocation Id * @return isActive Whether the allocation is active or not diff --git a/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol b/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol index 32b618f60..bd8da3508 100644 --- a/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol +++ b/packages/interfaces/contracts/contracts/rewards/IRewardsManager.sol @@ -1,10 +1,19 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title IRewardsManager + * @author Edge & Node + * @notice Interface for the RewardsManager contract that handles reward distribution + */ interface IRewardsManager { /** - * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment. + * @dev Stores accumulated rewards and snapshots related to a particular SubgraphDeployment + * @param accRewardsForSubgraph Accumulated rewards for the subgraph + * @param accRewardsForSubgraphSnapshot Snapshot of accumulated rewards for the subgraph + * @param accRewardsPerSignalSnapshot Snapshot of accumulated rewards per signal + * @param accRewardsPerAllocatedToken Accumulated rewards per allocated token */ struct Subgraph { uint256 accRewardsForSubgraph; @@ -15,43 +24,141 @@ interface IRewardsManager { // -- Config -- + /** + * @notice Set the issuance per block for rewards distribution + * @param issuancePerBlock The amount of tokens to issue per block + */ function setIssuancePerBlock(uint256 issuancePerBlock) external; + /** + * @notice Sets the minimum signaled tokens on a subgraph to start accruing rewards + * @dev Can be set to zero which means that this feature is not being used + * @param minimumSubgraphSignal Minimum signaled tokens + */ function setMinimumSubgraphSignal(uint256 minimumSubgraphSignal) external; + /** + * @notice Set the subgraph service address + * @param subgraphService Address of the subgraph service contract + */ function setSubgraphService(address subgraphService) external; + /** + * @notice Set the rewards eligibility oracle address + * @param newRewardsEligibilityOracle The address of the rewards eligibility oracle + */ + function setRewardsEligibilityOracle(address newRewardsEligibilityOracle) external; + // -- Denylist -- + /** + * @notice Set the subgraph availability oracle address + * @param subgraphAvailabilityOracle The address of the subgraph availability oracle + */ function setSubgraphAvailabilityOracle(address subgraphAvailabilityOracle) external; + /** + * @notice Set the denied status for a subgraph deployment + * @param subgraphDeploymentID The subgraph deployment ID + * @param deny True to deny, false to allow + */ function setDenied(bytes32 subgraphDeploymentID, bool deny) external; + /** + * @notice Check if a subgraph deployment is denied + * @param subgraphDeploymentID The subgraph deployment ID to check + * @return True if the subgraph is denied, false otherwise + */ function isDenied(bytes32 subgraphDeploymentID) external view returns (bool); // -- Getters -- + /** + * @notice Gets the effective issuance per block for rewards + * @dev Takes into account the issuance allocator if set + * @return The effective issuance per block + */ + function getRewardsIssuancePerBlock() external view returns (uint256); + + /** + * @notice Gets the issuance of rewards per signal since last updated + * @return newly accrued rewards per signal since last update + */ function getNewRewardsPerSignal() external view returns (uint256); + /** + * @notice Gets the currently accumulated rewards per signal + * @return Currently accumulated rewards per signal + */ function getAccRewardsPerSignal() external view returns (uint256); + /** + * @notice Get the accumulated rewards for a specific subgraph + * @param subgraphDeploymentID The subgraph deployment ID + * @return The accumulated rewards for the subgraph + */ function getAccRewardsForSubgraph(bytes32 subgraphDeploymentID) external view returns (uint256); + /** + * @notice Gets the accumulated rewards per allocated token for the subgraph + * @param subgraphDeploymentID Subgraph deployment + * @return Accumulated rewards per allocated token for the subgraph + * @return Accumulated rewards for subgraph + */ function getAccRewardsPerAllocatedToken(bytes32 subgraphDeploymentID) external view returns (uint256, uint256); + /** + * @notice Calculate current rewards for a given allocation on demand + * @param rewardsIssuer The rewards issuer contract + * @param allocationID Allocation + * @return Rewards amount for an allocation + */ function getRewards(address rewardsIssuer, address allocationID) external view returns (uint256); + /** + * @notice Calculate rewards based on tokens and accumulated rewards per allocated token + * @param tokens The number of tokens allocated + * @param accRewardsPerAllocatedToken The accumulated rewards per allocated token + * @return The calculated rewards amount + */ function calcRewards(uint256 tokens, uint256 accRewardsPerAllocatedToken) external pure returns (uint256); // -- Updates -- + /** + * @notice Updates the accumulated rewards per signal and save checkpoint block number + * @dev Must be called before `issuancePerBlock` or `total signalled GRT` changes. + * Called from the Curation contract on mint() and burn() + * @return Accumulated rewards per signal + */ function updateAccRewardsPerSignal() external returns (uint256); + /** + * @notice Pull rewards from the contract for a particular allocation + * @dev This function can only be called by the Staking contract. + * This function will mint the necessary tokens to reward based on the inflation calculation. + * @param allocationID Allocation + * @return Assigned rewards amount + */ function takeRewards(address allocationID) external returns (uint256); // -- Hooks -- + /** + * @notice Triggers an update of rewards for a subgraph + * @dev Must be called before `signalled GRT` on a subgraph changes. + * Hook called from the Curation contract on mint() and burn() + * @param subgraphDeploymentID Subgraph deployment + * @return Accumulated rewards for subgraph + */ function onSubgraphSignalUpdate(bytes32 subgraphDeploymentID) external returns (uint256); + /** + * @notice Triggers an update of rewards for a subgraph + * @dev Must be called before allocation on a subgraph changes. + * Hook called from the Staking contract on allocate() and close() + * @param subgraphDeploymentID Subgraph deployment + * @return Accumulated rewards per allocated token for a subgraph + */ function onSubgraphAllocationUpdate(bytes32 subgraphDeploymentID) external returns (uint256); } diff --git a/packages/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol b/packages/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol index aca3b0ffd..bbf5ea12c 100644 --- a/packages/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol +++ b/packages/interfaces/contracts/contracts/staking/IL1GraphTokenLockTransferTool.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; /** * @title Interface for the L1GraphTokenLockTransferTool contract - * @dev This interface defines the function to get the L2 wallet address for a given L1 token lock wallet. + * @author Edge & Node + * @notice This interface defines the function to get the L2 wallet address for a given L1 token lock wallet. * The Transfer Tool contract is implemented in the token-distribution repo: https://github.com/graphprotocol/token-distribution/pull/64 * and is only included here to provide support in L1Staking for the transfer of stake and delegation * owned by token lock contracts. See GIP-0046 for details: https://forum.thegraph.com/t/4023 diff --git a/packages/interfaces/contracts/contracts/staking/IL1Staking.sol b/packages/interfaces/contracts/contracts/staking/IL1Staking.sol index fd7859220..b29686af2 100644 --- a/packages/interfaces/contracts/contracts/staking/IL1Staking.sol +++ b/packages/interfaces/contracts/contracts/staking/IL1Staking.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; import { IStaking } from "./IStaking.sol"; @@ -8,6 +8,7 @@ import { IL1StakingBase } from "./IL1StakingBase.sol"; /** * @title Interface for the L1 Staking contract + * @author Edge & Node * @notice This is the interface that should be used when interacting with the L1 Staking contract. * It extends the IStaking interface with the functions that are specific to L1, adding the transfer tools * to send stake and delegation to L2. diff --git a/packages/interfaces/contracts/contracts/staking/IL1StakingBase.sol b/packages/interfaces/contracts/contracts/staking/IL1StakingBase.sol index 9ed037258..3443a39fd 100644 --- a/packages/interfaces/contracts/contracts/staking/IL1StakingBase.sol +++ b/packages/interfaces/contracts/contracts/staking/IL1StakingBase.sol @@ -1,25 +1,41 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { IL1GraphTokenLockTransferTool } from "./IL1GraphTokenLockTransferTool.sol"; /** * @title Base interface for the L1Staking contract. + * @author Edge & Node * @notice This interface is used to define the transfer tools that are implemented in L1Staking. * @dev Note it includes only the L1-specific functionality, not the full IStaking interface. */ interface IL1StakingBase { - /// @dev Emitted when an indexer transfers their stake to L2. - /// This can happen several times as indexers can transfer partial stake. + /** + * @notice Emitted when an indexer transfers their stake to L2. + * This can happen several times as indexers can transfer partial stake. + * @param indexer Address of the indexer on L1 + * @param l2Indexer Address of the indexer on L2 + * @param transferredStakeTokens Amount of stake tokens transferred + */ event IndexerStakeTransferredToL2( address indexed indexer, address indexed l2Indexer, uint256 transferredStakeTokens ); - /// @dev Emitted when a delegator transfers their delegation to L2 + /** + * @notice Emitted when a delegator transfers their delegation to L2 + * @param delegator Address of the delegator on L1 + * @param l2Delegator Address of the delegator on L2 + * @param indexer Address of the indexer on L1 + * @param l2Indexer Address of the indexer on L2 + * @param transferredDelegationTokens Amount of delegation tokens transferred + */ event DelegationTransferredToL2( address indexed delegator, address indexed l2Delegator, @@ -28,10 +44,17 @@ interface IL1StakingBase { uint256 transferredDelegationTokens ); - /// @dev Emitted when the L1GraphTokenLockTransferTool is set + /** + * @notice Emitted when the L1GraphTokenLockTransferTool is set + * @param l1GraphTokenLockTransferTool Address of the L1GraphTokenLockTransferTool contract + */ event L1GraphTokenLockTransferToolSet(address l1GraphTokenLockTransferTool); - /// @dev Emitted when a delegator unlocks their tokens ahead of time because the indexer has transferred to L2 + /** + * @notice Emitted when a delegator unlocks their tokens ahead of time because the indexer has transferred to L2 + * @param indexer Address of the indexer that transferred to L2 + * @param delegator Address of the delegator unlocking their tokens + */ event StakeDelegatedUnlockedDueToL2Transfer(address indexed indexer, address indexed delegator); /** diff --git a/packages/interfaces/contracts/contracts/staking/IStaking.sol b/packages/interfaces/contracts/contracts/staking/IStaking.sol index c18e382b3..c644009b6 100644 --- a/packages/interfaces/contracts/contracts/staking/IStaking.sol +++ b/packages/interfaces/contracts/contracts/staking/IStaking.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; import { IStakingBase } from "./IStakingBase.sol"; @@ -10,6 +10,7 @@ import { IManaged } from "../governance/IManaged.sol"; /** * @title Interface for the Staking contract + * @author Edge & Node * @notice This is the interface that should be used when interacting with the Staking contract. * @dev Note that Staking doesn't actually inherit this interface. This is because of * the custom setup of the Staking contract where part of the functionality is implemented diff --git a/packages/interfaces/contracts/contracts/staking/IStakingBase.sol b/packages/interfaces/contracts/contracts/staking/IStakingBase.sol index d7728d049..6af61c077 100644 --- a/packages/interfaces/contracts/contracts/staking/IStakingBase.sol +++ b/packages/interfaces/contracts/contracts/staking/IStakingBase.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { IStakingData } from "./IStakingData.sol"; /** * @title Base interface for the Staking contract. + * @author Edge & Node + * @notice Base interface for the Staking contract. * @dev This interface includes only what's implemented in the base Staking contract. * It does not include the L1 and L2 specific functionality. It also does not include * several functions that are implemented in the StakingExtension contract, and are called @@ -15,25 +20,38 @@ import { IStakingData } from "./IStakingData.sol"; */ interface IStakingBase is IStakingData { /** - * @dev Emitted when `indexer` stakes `tokens` amount. + * @notice Emitted when `indexer` stakes `tokens` amount. + * @param indexer Address of the indexer + * @param tokens Amount of tokens staked */ event StakeDeposited(address indexed indexer, uint256 tokens); /** - * @dev Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + * @notice Emitted when `indexer` unstaked and locked `tokens` amount until `until` block. + * @param indexer Address of the indexer + * @param tokens Amount of tokens locked + * @param until Block number until which tokens are locked */ event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); /** - * @dev Emitted when `indexer` withdrew `tokens` staked. + * @notice Emitted when `indexer` withdrew `tokens` staked. + * @param indexer Address of the indexer + * @param tokens Amount of tokens withdrawn */ event StakeWithdrawn(address indexed indexer, uint256 tokens); /** - * @dev Emitted when `indexer` allocated `tokens` amount to `subgraphDeploymentID` + * @notice 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. + * @param indexer Address of the indexer + * @param subgraphDeploymentID Subgraph deployment ID + * @param epoch Epoch when allocation was created + * @param tokens Amount of tokens allocated + * @param allocationID Allocation identifier + * @param metadata IPFS hash for additional allocation information */ event AllocationCreated( address indexed indexer, @@ -45,10 +63,18 @@ interface IStakingBase is IStakingData { ); /** - * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * @notice Emitted when `indexer` close an allocation in `epoch` for `allocationID`. * An amount of `tokens` get unallocated from `subgraphDeploymentID`. * This event also emits the POI (proof of indexing) submitted by the indexer. * `isPublic` is true if the sender was someone other than the indexer. + * @param indexer Address of the indexer + * @param subgraphDeploymentID Subgraph deployment ID + * @param epoch Epoch when allocation was closed + * @param tokens Amount of tokens unallocated + * @param allocationID Allocation identifier + * @param sender Address that closed the allocation + * @param poi Proof of indexing submitted + * @param isPublic True if closed by someone other than the indexer */ event AllocationClosed( address indexed indexer, @@ -62,12 +88,23 @@ interface IStakingBase is IStakingData { ); /** - * @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. + * @notice Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. * `epoch` is the protocol epoch the rebate was collected on * The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees` * is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt. * `queryRebates` is the amount distributed to the `indexer` with `delegationFees` collected * and sent to the delegation pool. + * @param assetHolder Address providing the rebate tokens + * @param indexer Address of the indexer collecting the rebate + * @param subgraphDeploymentID Subgraph deployment ID + * @param allocationID Allocation identifier + * @param epoch Epoch when rebate was collected + * @param tokens Total amount of tokens in the rebate + * @param protocolTax Amount burned as protocol tax + * @param curationFees Amount distributed to curators + * @param queryFees Amount available for rebate after fees + * @param queryRebates Amount distributed to the indexer + * @param delegationRewards Amount distributed to delegators */ event RebateCollected( address assetHolder, @@ -84,7 +121,11 @@ interface IStakingBase is IStakingData { ); /** - * @dev Emitted when `indexer` update the delegation parameters for its delegation pool. + * @notice Emitted when `indexer` update the delegation parameters for its delegation pool. + * @param indexer Address of the indexer + * @param indexingRewardCut Percentage of indexing rewards left for the indexer + * @param queryFeeCut Percentage of query fees left for the indexer + * @param __DEPRECATED_cooldownBlocks Deprecated parameter (no longer used) */ event DelegationParametersUpdated( address indexed indexer, @@ -94,18 +135,24 @@ interface IStakingBase is IStakingData { ); /** - * @dev Emitted when `indexer` set `operator` access. + * @notice Emitted when `indexer` set `operator` access. + * @param indexer Address of the indexer + * @param operator Address of the operator + * @param allowed Whether the operator is authorized */ event SetOperator(address indexed indexer, address indexed operator, bool allowed); /** - * @dev Emitted when `indexer` set an address to receive rewards. + * @notice Emitted when `indexer` set an address to receive rewards. + * @param indexer Address of the indexer + * @param destination Address to receive rewards */ event SetRewardsDestination(address indexed indexer, address indexed destination); /** - * @dev Emitted when `extensionImpl` was set as the address of the StakingExtension contract + * @notice Emitted when `extensionImpl` was set as the address of the StakingExtension contract * to which extended functionality is delegated. + * @param extensionImpl Address of the StakingExtension implementation */ event ExtensionImplementationSet(address indexed extensionImpl); @@ -260,12 +307,9 @@ interface IStakingBase is IStakingData { * @notice Set the delegation parameters for the caller. * @param indexingRewardCut Percentage of indexing rewards left for the indexer * @param queryFeeCut Percentage of query fees left for the indexer + * @param cooldownBlocks Deprecated cooldown blocks parameter (no longer used) */ - function setDelegationParameters( - uint32 indexingRewardCut, - uint32 queryFeeCut, - uint32 // cooldownBlocks, deprecated - ) external; + function setDelegationParameters(uint32 indexingRewardCut, uint32 queryFeeCut, uint32 cooldownBlocks) external; /** * @notice Allocate available tokens to a subgraph deployment. @@ -362,18 +406,29 @@ interface IStakingBase is IStakingData { function getAllocation(address allocationID) external view returns (Allocation memory); /** + * @notice Get the allocation data for the rewards manager * @dev New function to get the allocation data for the rewards manager * @dev Note that this is only to make tests pass, as the staking contract with * this changes will never get deployed. HorizonStaking is taking it's place. + * @param allocationID The allocation ID + * @return active Whether the allocation is active + * @return indexer The indexer address + * @return subgraphDeploymentID The subgraph deployment ID + * @return tokens The allocated tokens + * @return createdAtEpoch The epoch when allocation was created + * @return closedAtEpoch The epoch when allocation was closed */ function getAllocationData( address allocationID ) external view returns (bool, address, bytes32, uint256, uint256, uint256); /** + * @notice Get the allocation active status for the rewards manager * @dev New function to get the allocation active status for the rewards manager * @dev Note that this is only to make tests pass, as the staking contract with * this changes will never get deployed. HorizonStaking is taking it's place. + * @param allocationID The allocation identifier + * @return True if the allocation is active, false otherwise */ function isActiveAllocation(address allocationID) external view returns (bool); diff --git a/packages/interfaces/contracts/contracts/staking/IStakingData.sol b/packages/interfaces/contracts/contracts/staking/IStakingData.sol index 7db4d0ca1..59acd96ff 100644 --- a/packages/interfaces/contracts/contracts/staking/IStakingData.sol +++ b/packages/interfaces/contracts/contracts/staking/IStakingData.sol @@ -1,15 +1,25 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; /** * @title Staking Data interface - * @dev This interface defines some structures used by the Staking contract. + * @author Edge & Node + * @notice This interface defines some structures used by the Staking contract. */ interface IStakingData { /** * @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment * An allocation is created in the allocate() function and closed in closeAllocation() + * @param indexer Address of the indexer that owns the allocation + * @param subgraphDeploymentID Subgraph deployment ID being allocated to + * @param tokens Tokens allocated to a SubgraphDeployment + * @param createdAtEpoch Epoch when it was created + * @param closedAtEpoch Epoch when it was closed + * @param collectedFees Collected fees for the allocation + * @param __DEPRECATED_effectiveAllocation Deprecated field for effective allocation + * @param accRewardsPerAllocatedToken Snapshot used for reward calc + * @param distributedRebates Collected rebates that have been rebated */ struct Allocation { address indexer; @@ -27,6 +37,13 @@ interface IStakingData { /** * @dev Delegation pool information. One per indexer. + * @param __DEPRECATED_cooldownBlocks Deprecated field for cooldown blocks + * @param indexingRewardCut Indexing reward cut in PPM + * @param queryFeeCut Query fee cut in PPM + * @param updatedAtBlock Block when the pool was last updated + * @param tokens Total tokens as pool reserves + * @param shares Total shares minted in the pool + * @param delegators Mapping of delegator => Delegation */ struct DelegationPool { uint32 __DEPRECATED_cooldownBlocks; // solhint-disable-line var-name-mixedcase @@ -40,6 +57,9 @@ interface IStakingData { /** * @dev Individual delegation data of a delegator in a pool. + * @param shares Shares owned by a delegator in the pool + * @param tokensLocked Tokens locked for undelegation + * @param tokensLockedUntil Epoch when locked tokens can be withdrawn */ struct Delegation { uint256 shares; // Shares owned by a delegator in the pool @@ -49,6 +69,10 @@ interface IStakingData { /** * @dev Rebates parameters. Used to avoid stack too deep errors in Staking initialize function. + * @param alphaNumerator Alpha parameter numerator for rebate calculation + * @param alphaDenominator Alpha parameter denominator for rebate calculation + * @param lambdaNumerator Lambda parameter numerator for rebate calculation + * @param lambdaDenominator Lambda parameter denominator for rebate calculation */ struct RebatesParameters { uint32 alphaNumerator; diff --git a/packages/interfaces/contracts/contracts/staking/IStakingExtension.sol b/packages/interfaces/contracts/contracts/staking/IStakingExtension.sol index 0a13ab744..72c107b0a 100644 --- a/packages/interfaces/contracts/contracts/staking/IStakingExtension.sol +++ b/packages/interfaces/contracts/contracts/staking/IStakingExtension.sol @@ -1,14 +1,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { IStakingData } from "./IStakingData.sol"; import { IStakes } from "./libs/IStakes.sol"; /** * @title Interface for the StakingExtension contract - * @dev This interface defines the events and functions implemented + * @author Edge & Node + * @notice This interface defines the events and functions implemented * in the StakingExtension contract, which is used to extend the functionality * of the Staking contract while keeping it within the 24kB mainnet size limit. * In particular, this interface includes delegation functions and various storage @@ -18,6 +22,12 @@ interface IStakingExtension is IStakingData { /** * @dev DelegationPool struct as returned by delegationPools(), since * the original DelegationPool in IStakingData.sol contains a nested mapping. + * @param __DEPRECATED_cooldownBlocks Deprecated field for cooldown blocks + * @param indexingRewardCut Indexing reward cut in PPM + * @param queryFeeCut Query fee cut in PPM + * @param updatedAtBlock Block when the pool was last updated + * @param tokens Total tokens as pool reserves + * @param shares Total shares minted in the pool */ struct DelegationPoolReturn { uint32 __DEPRECATED_cooldownBlocks; // solhint-disable-line var-name-mixedcase @@ -29,14 +39,23 @@ interface IStakingExtension is IStakingData { } /** - * @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator + * @notice Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator * gets `shares` for the delegation pool proportionally to the tokens staked. + * @param indexer Address of the indexer receiving the delegation + * @param delegator Address of the delegator + * @param tokens Amount of tokens delegated + * @param shares Amount of shares issued to the delegator */ event StakeDelegated(address indexed indexer, address indexed delegator, uint256 tokens, uint256 shares); /** - * @dev Emitted when `delegator` undelegated `tokens` from `indexer`. + * @notice Emitted when `delegator` undelegated `tokens` from `indexer`. * Tokens get locked for withdrawal after a period of time. + * @param indexer Address of the indexer from which tokens are undelegated + * @param delegator Address of the delegator + * @param tokens Amount of tokens undelegated + * @param shares Amount of shares returned + * @param until Epoch until which tokens are locked */ event StakeDelegatedLocked( address indexed indexer, @@ -47,18 +66,28 @@ interface IStakingExtension is IStakingData { ); /** - * @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + * @notice Emitted when `delegator` withdrew delegated `tokens` from `indexer`. + * @param indexer Address of the indexer from which tokens are withdrawn + * @param delegator Address of the delegator + * @param tokens Amount of tokens withdrawn */ event StakeDelegatedWithdrawn(address indexed indexer, address indexed delegator, uint256 tokens); /** - * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * @notice Emitted when `indexer` was slashed for a total of `tokens` amount. * Tracks `reward` amount of tokens given to `beneficiary`. + * @param indexer Address of the indexer that was slashed + * @param tokens Total amount of tokens slashed + * @param reward Amount of tokens given as reward + * @param beneficiary Address receiving the reward */ event StakeSlashed(address indexed indexer, uint256 tokens, uint256 reward, address beneficiary); /** - * @dev Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + * @notice Emitted when `caller` set `slasher` address as `allowed` to slash stakes. + * @param caller Address that updated the slasher status + * @param slasher Address of the slasher + * @param allowed Whether the slasher is allowed to slash */ event SlasherUpdate(address indexed caller, address indexed slasher, bool allowed); @@ -67,16 +96,16 @@ interface IStakingExtension is IStakingData { * If set to 10 it means the indexer can use up to 10x the indexer staked amount * from their delegated tokens * @dev This function is only callable by the governor - * @param delegationRatio Delegation capacity multiplier + * @param newDelegationRatio Delegation capacity multiplier */ - function setDelegationRatio(uint32 delegationRatio) external; + function setDelegationRatio(uint32 newDelegationRatio) external; /** * @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating. * @dev This function is only callable by the governor - * @param delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating + * @param newDelegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating */ - function setDelegationUnbondingPeriod(uint32 delegationUnbondingPeriod) external; + function setDelegationUnbondingPeriod(uint32 newDelegationUnbondingPeriod) external; /** * @notice Set a delegation tax percentage to burn when delegated funds are deposited. @@ -114,6 +143,7 @@ interface IStakingExtension is IStakingData { * re-delegate to a new indexer. * @param indexer Withdraw available tokens delegated to indexer * @param newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address + * @return Amount of tokens withdrawn */ function withdrawDelegated(address indexer, address newIndexer) external returns (uint256); diff --git a/packages/interfaces/contracts/contracts/staking/libs/IStakes.sol b/packages/interfaces/contracts/contracts/staking/libs/IStakes.sol index 701336409..3635a1263 100644 --- a/packages/interfaces/contracts/contracts/staking/libs/IStakes.sol +++ b/packages/interfaces/contracts/contracts/staking/libs/IStakes.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; pragma abicoder v2; +/** + * @title Interface for staking data structures + * @author Edge & Node + * @notice Defines the data structures used for indexer staking + */ interface IStakes { struct Indexer { uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer) diff --git a/packages/interfaces/contracts/contracts/token/IGraphToken.sol b/packages/interfaces/contracts/contracts/token/IGraphToken.sol index 8d5d1b845..5ad15d9f6 100644 --- a/packages/interfaces/contracts/contracts/token/IGraphToken.sol +++ b/packages/interfaces/contracts/contracts/token/IGraphToken.sol @@ -1,26 +1,68 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +// Solhint linting fails for 0.8.0. +// solhint-disable-next-line import-path-check import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +/** + * @title IGraphToken + * @author Edge & Node + * @notice Interface for the Graph Token contract + * @dev Extends IERC20 with additional functionality for minting, burning, and permit + */ interface IGraphToken is IERC20 { // -- Mint and Burn -- + /** + * @notice Burns tokens from the caller's account + * @param amount The amount of tokens to burn + */ function burn(uint256 amount) external; + /** + * @notice Burns tokens from a specified account (requires allowance) + * @param from The account to burn tokens from + * @param amount The amount of tokens to burn + */ function burnFrom(address from, uint256 amount) external; + /** + * @notice Mints new tokens to a specified account + * @dev Only callable by accounts with minter role + * @param to The account to mint tokens to + * @param amount The amount of tokens to mint + */ function mint(address to, uint256 amount) external; // -- Mint Admin -- + /** + * @notice Adds a new minter account + * @dev Only callable by accounts with appropriate permissions + * @param account The account to grant minter role to + */ function addMinter(address account) external; + /** + * @notice Removes minter role from an account + * @dev Only callable by accounts with appropriate permissions + * @param account The account to revoke minter role from + */ function removeMinter(address account) external; + /** + * @notice Renounces minter role for the caller + * @dev Allows a minter to voluntarily give up their minting privileges + */ function renounceMinter() external; + /** + * @notice Checks if an account has minter role + * @param account The account to check + * @return True if the account is a minter, false otherwise + */ function isMinter(address account) external view returns (bool); // -- Permit -- @@ -47,7 +89,19 @@ interface IGraphToken is IERC20 { // -- Allowance -- + /** + * @notice Increases the allowance granted to a spender + * @param spender The account whose allowance will be increased + * @param addedValue The amount to increase the allowance by + * @return True if the operation succeeded + */ function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + /** + * @notice Decreases the allowance granted to a spender + * @param spender The account whose allowance will be decreased + * @param subtractedValue The amount to decrease the allowance by + * @return True if the operation succeeded + */ function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); } diff --git a/packages/interfaces/contracts/contracts/upgrades/IGraphProxy.sol b/packages/interfaces/contracts/contracts/upgrades/IGraphProxy.sol index 108708c03..641f41e4f 100644 --- a/packages/interfaces/contracts/contracts/upgrades/IGraphProxy.sol +++ b/packages/interfaces/contracts/contracts/upgrades/IGraphProxy.sol @@ -1,19 +1,77 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +/** + * @title Graph Proxy Interface + * @author Edge & Node + * @notice Interface for the Graph Proxy contract that handles upgradeable proxy functionality + */ interface IGraphProxy { + /** + * @notice Get the current admin. + * + * @dev 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. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + * + * @return adminAddress The address of the current admin + */ function admin() external returns (address); + /** + * @notice Change the admin of the proxy. + * + * @dev NOTE: Only the admin can call this function. + * + * @param newAdmin Address of the new admin + */ function setAdmin(address newAdmin) external; + /** + * @notice Get the current implementation. + * + * @dev 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` + * + * @return implementationAddress The address of the current implementation for this proxy + */ function implementation() external returns (address); + /** + * @notice Get the current pending implementation. + * + * @dev 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` + * + * @return pendingImplementationAddress The address of the current pending implementation for this proxy + */ function pendingImplementation() external returns (address); + /** + * @notice Upgrades to a new implementation contract. + * @dev NOTE: Only the admin can call this function. + * @param newImplementation Address of implementation contract + */ function upgradeTo(address newImplementation) external; + /** + * @notice Admin function for new implementation to accept its role as implementation. + */ function acceptUpgrade() external; + /** + * @notice Admin function for new implementation to accept its role as implementation, + * calling a function on the new implementation. + * @param data Calldata (including selector) for the function to delegatecall into the implementation + */ function acceptUpgradeAndCall(bytes calldata data) external; } diff --git a/packages/interfaces/contracts/contracts/upgrades/IGraphProxyAdmin.sol b/packages/interfaces/contracts/contracts/upgrades/IGraphProxyAdmin.sol index 44e0df0e4..7c6cfad6c 100644 --- a/packages/interfaces/contracts/contracts/upgrades/IGraphProxyAdmin.sol +++ b/packages/interfaces/contracts/contracts/upgrades/IGraphProxyAdmin.sol @@ -1,36 +1,87 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; import { IGraphProxy } from "./IGraphProxy.sol"; import { IGoverned } from "../governance/IGoverned.sol"; /** * @title IGraphProxyAdmin - * @dev GraphProxyAdmin contract interface + * @author Edge & Node + * @notice GraphProxyAdmin contract interface for managing proxy contracts * @dev Note that this interface is not used by the contract implementation, just used for types and abi generation * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ interface IGraphProxyAdmin is IGoverned { + /** + * @notice Get the implementation address of a proxy + * @param proxy The proxy contract to query + * @return The implementation address + */ function getProxyImplementation(IGraphProxy proxy) external view returns (address); + /** + * @notice Get the pending implementation address of a proxy + * @param proxy The proxy contract to query + * @return The pending implementation address + */ function getProxyPendingImplementation(IGraphProxy proxy) external view returns (address); + /** + * @notice Get the admin address of a proxy + * @param proxy The proxy contract to query + * @return The admin address + */ function getProxyAdmin(IGraphProxy proxy) external view returns (address); + /** + * @notice Change the admin of a proxy contract + * @param proxy The proxy contract to modify + * @param newAdmin The new admin address + */ function changeProxyAdmin(IGraphProxy proxy, address newAdmin) external; + /** + * @notice Upgrade a proxy to a new implementation + * @param proxy The proxy contract to upgrade + * @param implementation The new implementation address + */ function upgrade(IGraphProxy proxy, address implementation) external; + /** + * @notice Upgrade a proxy to a new implementation + * @param proxy The proxy contract to upgrade + * @param implementation The new implementation address + */ function upgradeTo(IGraphProxy proxy, address implementation) external; + /** + * @notice Upgrade a proxy to a new implementation and call a function + * @param proxy The proxy contract to upgrade + * @param implementation The new implementation address + * @param data The calldata to execute on the new implementation + */ function upgradeToAndCall(IGraphProxy proxy, address implementation, bytes calldata data) external; + /** + * @notice Accept ownership of a proxy contract + * @param proxy The proxy contract to accept + */ function acceptProxy(IGraphProxy proxy) external; + /** + * @notice Accept ownership of a proxy contract and call a function + * @param proxy The proxy contract to accept + * @param data The calldata to execute after accepting + */ function acceptProxyAndCall(IGraphProxy proxy, bytes calldata data) external; // storage + + /** + * @notice Get the governor address + * @return The address of the governor + */ function governor() external view returns (address); } diff --git a/packages/interfaces/contracts/data-service/IDataService.sol b/packages/interfaces/contracts/data-service/IDataService.sol index 778987f71..8fa18063b 100644 --- a/packages/interfaces/contracts/data-service/IDataService.sol +++ b/packages/interfaces/contracts/data-service/IDataService.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IGraphPayments } from "../horizon/IGraphPayments.sol"; /** * @title Interface of the base {DataService} contract as defined by the Graph Horizon specification. + * @author Edge & Node * @notice This interface provides a guardrail for contracts that use the Data Service framework * to implement a data service on Graph Horizon. Much of the specification is intentionally loose * to allow for greater flexibility when designing a data service. It's not possible to guarantee that diff --git a/packages/interfaces/contracts/data-service/IDataServiceFees.sol b/packages/interfaces/contracts/data-service/IDataServiceFees.sol index 9d235f4f7..9cba91d7a 100644 --- a/packages/interfaces/contracts/data-service/IDataServiceFees.sol +++ b/packages/interfaces/contracts/data-service/IDataServiceFees.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IDataService } from "./IDataService.sol"; /** * @title Interface for the {DataServiceFees} contract. + * @author Edge & Node * @notice Extension for the {IDataService} contract to handle payment collateralization * using a Horizon provision. * diff --git a/packages/interfaces/contracts/data-service/IDataServicePausable.sol b/packages/interfaces/contracts/data-service/IDataServicePausable.sol index 906e864a8..c95ba124a 100644 --- a/packages/interfaces/contracts/data-service/IDataServicePausable.sol +++ b/packages/interfaces/contracts/data-service/IDataServicePausable.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IDataService } from "./IDataService.sol"; /** * @title Interface for the {DataServicePausable} contract. + * @author Edge & Node * @notice Extension for the {IDataService} contract, adds pausing functionality * to the data service. Pausing is controlled by privileged accounts called * pause guardians. diff --git a/packages/interfaces/contracts/data-service/IDataServiceRescuable.sol b/packages/interfaces/contracts/data-service/IDataServiceRescuable.sol index f2cd7b06e..4607e664c 100644 --- a/packages/interfaces/contracts/data-service/IDataServiceRescuable.sol +++ b/packages/interfaces/contracts/data-service/IDataServiceRescuable.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IDataService } from "./IDataService.sol"; /** * @title Interface for the {IDataServicePausable} contract. + * @author Edge & Node * @notice Extension for the {IDataService} contract, adds the ability to rescue * any ERC20 token or ETH from the contract, controlled by a rescuer privileged role. * @custom:security-contact Please email security+contracts@thegraph.com if you find any diff --git a/packages/interfaces/contracts/horizon/IAuthorizable.sol b/packages/interfaces/contracts/horizon/IAuthorizable.sol index 7a7a77798..e83dee62b 100644 --- a/packages/interfaces/contracts/horizon/IAuthorizable.sol +++ b/packages/interfaces/contracts/horizon/IAuthorizable.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events +// solhint-disable gas-struct-packing /** * @title Interface for the {Authorizable} contract + * @author Edge & Node * @notice Implements an authorization scheme that allows authorizers to * authorize signers to sign on their behalf. * @custom:security-contact Please email security+contracts@thegraph.com if you find any diff --git a/packages/interfaces/contracts/horizon/IGraphPayments.sol b/packages/interfaces/contracts/horizon/IGraphPayments.sol index 7a583f883..39955da88 100644 --- a/packages/interfaces/contracts/horizon/IGraphPayments.sol +++ b/packages/interfaces/contracts/horizon/IGraphPayments.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Interface for the {GraphPayments} contract + * @author Edge & Node * @notice This contract is part of the Graph Horizon payments protocol. It's designed * to pull funds (GRT) from the {PaymentsEscrow} and distribute them according to a * set of pre established rules. diff --git a/packages/interfaces/contracts/horizon/IGraphTallyCollector.sol b/packages/interfaces/contracts/horizon/IGraphTallyCollector.sol index 86d0ebc3a..60b915cc4 100644 --- a/packages/interfaces/contracts/horizon/IGraphTallyCollector.sol +++ b/packages/interfaces/contracts/horizon/IGraphTallyCollector.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; import { IPaymentsCollector } from "./IPaymentsCollector.sol"; import { IGraphPayments } from "./IGraphPayments.sol"; @@ -7,6 +7,7 @@ import { IAuthorizable } from "./IAuthorizable.sol"; /** * @title Interface for the {GraphTallyCollector} contract + * @author Edge & Node * @dev Implements the {IPaymentCollector} interface as defined by the Graph * Horizon payments protocol. * @notice Implements a payments collector contract that can be used to collect @@ -49,8 +50,8 @@ interface IGraphTallyCollector is IPaymentsCollector, IAuthorizable { * @notice Emitted when a RAV is collected * @param collectionId The ID of the collection "bucket" the RAV belongs to. * @param payer The address of the payer - * @param dataService The address of the data service * @param serviceProvider The address of the service provider + * @param dataService The address of the data service * @param timestampNs The timestamp of the RAV * @param valueAggregate The total amount owed to the service provider * @param metadata Arbitrary metadata @@ -122,14 +123,14 @@ interface IGraphTallyCollector is IPaymentsCollector, IAuthorizable { ) external returns (uint256); /** - * @dev Recovers the signer address of a signed ReceiptAggregateVoucher (RAV). + * @notice Recovers the signer address of a signed ReceiptAggregateVoucher (RAV). * @param signedRAV The SignedRAV containing the RAV and its signature. * @return The address of the signer. */ function recoverRAVSigner(SignedRAV calldata signedRAV) external view returns (address); /** - * @dev Computes the hash of a ReceiptAggregateVoucher (RAV). + * @notice Computes the hash of a ReceiptAggregateVoucher (RAV). * @param rav The RAV for which to compute the hash. * @return The hash of the RAV. */ diff --git a/packages/interfaces/contracts/horizon/IHorizonStaking.sol b/packages/interfaces/contracts/horizon/IHorizonStaking.sol index 0ba4e26b3..4e680a1e5 100644 --- a/packages/interfaces/contracts/horizon/IHorizonStaking.sol +++ b/packages/interfaces/contracts/horizon/IHorizonStaking.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; import { IHorizonStakingTypes } from "./internal/IHorizonStakingTypes.sol"; import { IHorizonStakingMain } from "./internal/IHorizonStakingMain.sol"; @@ -9,6 +9,7 @@ import { IHorizonStakingExtension } from "./internal/IHorizonStakingExtension.so /** * @title Complete interface for the Horizon Staking contract + * @author Edge & Node * @notice This interface exposes all functions implemented by the {HorizonStaking} contract and its extension * {HorizonStakingExtension} as well as the custom data types used by the contract. * @dev Use this interface to interact with the Horizon Staking contract. diff --git a/packages/interfaces/contracts/horizon/IPaymentsCollector.sol b/packages/interfaces/contracts/horizon/IPaymentsCollector.sol index d37688462..a16aef951 100644 --- a/packages/interfaces/contracts/horizon/IPaymentsCollector.sol +++ b/packages/interfaces/contracts/horizon/IPaymentsCollector.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; import { IGraphPayments } from "./IGraphPayments.sol"; /** * @title Interface for a payments collector contract as defined by Graph Horizon payments protocol + * @author Edge & Node * @notice Contracts implementing this interface can be used with the payments protocol. First, a payer must * approve the collector to collect payments on their behalf. Only then can payment collection be initiated * using the collector contract. diff --git a/packages/interfaces/contracts/horizon/IPaymentsEscrow.sol b/packages/interfaces/contracts/horizon/IPaymentsEscrow.sol index 601783b12..9dbe9906a 100644 --- a/packages/interfaces/contracts/horizon/IPaymentsEscrow.sol +++ b/packages/interfaces/contracts/horizon/IPaymentsEscrow.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; import { IGraphPayments } from "./IGraphPayments.sol"; /** * @title Interface for the {PaymentsEscrow} contract + * @author Edge & Node * @notice This contract is part of the Graph Horizon payments protocol. It holds the funds (GRT) * for payments made through the payments protocol for services provided * via a Graph Horizon data service. diff --git a/packages/interfaces/contracts/horizon/internal/IHorizonStakingBase.sol b/packages/interfaces/contracts/horizon/internal/IHorizonStakingBase.sol index ba050ae3a..c48f20099 100644 --- a/packages/interfaces/contracts/horizon/internal/IHorizonStakingBase.sol +++ b/packages/interfaces/contracts/horizon/internal/IHorizonStakingBase.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IHorizonStakingTypes } from "./IHorizonStakingTypes.sol"; import { IGraphPayments } from "../IGraphPayments.sol"; @@ -9,6 +12,7 @@ import { ILinkedList } from "./ILinkedList.sol"; /** * @title Interface for the {HorizonStakingBase} contract. + * @author Edge & Node * @notice Provides getters for {HorizonStaking} and {HorizonStakingExtension} storage variables. * @dev Most functions operate over {HorizonStaking} provisions. To uniquely identify a provision * functions take `serviceProvider` and `verifier` addresses. diff --git a/packages/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol b/packages/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol index d590a76a5..d487b2eca 100644 --- a/packages/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol +++ b/packages/interfaces/contracts/horizon/internal/IHorizonStakingExtension.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IRewardsIssuer } from "../../contracts/rewards/IRewardsIssuer.sol"; /** * @title Interface for {HorizonStakingExtension} contract. + * @author Edge & Node * @notice Provides functions for managing legacy allocations. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. @@ -50,7 +54,7 @@ interface IHorizonStakingExtension is IRewardsIssuer { } /** - * @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`. + * @notice Emitted when `indexer` close an allocation in `epoch` for `allocationID`. * An amount of `tokens` get unallocated from `subgraphDeploymentID`. * This event also emits the POI (proof of indexing) submitted by the indexer. * `isPublic` is true if the sender was someone other than the indexer. @@ -75,7 +79,7 @@ interface IHorizonStakingExtension is IRewardsIssuer { ); /** - * @dev Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. + * @notice Emitted when `indexer` collects a rebate on `subgraphDeploymentID` for `allocationID`. * `epoch` is the protocol epoch the rebate was collected on * The rebate is for `tokens` amount which are being provided by `assetHolder`; `queryFees` * is the amount up for rebate after `curationFees` are distributed and `protocolTax` is burnt. @@ -108,7 +112,7 @@ interface IHorizonStakingExtension is IRewardsIssuer { ); /** - * @dev Emitted when `indexer` was slashed for a total of `tokens` amount. + * @notice Emitted when `indexer` was slashed for a total of `tokens` amount. * Tracks `reward` amount of tokens given to `beneficiary`. * @param indexer The indexer address * @param tokens The amount of tokens slashed @@ -128,7 +132,7 @@ interface IHorizonStakingExtension is IRewardsIssuer { function closeAllocation(address allocationID, bytes32 poi) external; /** - * @dev Collect and rebate query fees to the indexer + * @notice Collect and rebate query fees to the indexer * This function will accept calls with zero tokens. * We use an exponential rebate formula to calculate the amount of tokens to rebate to the indexer. * This implementation allows collecting multiple times on the same allocation, keeping track of the diff --git a/packages/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol b/packages/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol index 1a84bcd67..19c1e1cf8 100644 --- a/packages/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol +++ b/packages/interfaces/contracts/horizon/internal/IHorizonStakingMain.sol @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IGraphPayments } from "../IGraphPayments.sol"; import { IHorizonStakingTypes } from "./IHorizonStakingTypes.sol"; /** * @title Inferface for the {HorizonStaking} contract. + * @author Edge & Node * @notice Provides functions for managing stake, provisions, delegations, and slashing. * @dev Note that this interface only includes the functions implemented by {HorizonStaking} contract, * and not those implemented by {HorizonStakingExtension}. @@ -110,7 +114,7 @@ interface IHorizonStakingMain { ); /** - * @dev Emitted when an operator is allowed or denied by a service provider for a particular verifier + * @notice Emitted when an operator is allowed or denied by a service provider for a particular verifier * @param serviceProvider The address of the service provider * @param verifier The address of the verifier * @param operator The address of the operator @@ -289,12 +293,12 @@ interface IHorizonStakingMain { /** * @notice Emitted when a series of thaw requests are fulfilled. + * @param requestType The type of thaw request * @param serviceProvider The address of the service provider * @param verifier The address of the verifier * @param owner The address of the owner of the thaw requests * @param thawRequestsFulfilled The number of thaw requests fulfilled * @param tokens The total amount of tokens being released - * @param requestType The type of thaw request */ event ThawRequestsFulfilled( IHorizonStakingTypes.ThawRequestType indexed requestType, diff --git a/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol b/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol index 0ab84fc1b..e8fff211b 100644 --- a/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol +++ b/packages/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Defines the data types used in the Horizon staking contract + * @author Edge & Node + * @notice Interface defining data types and structures for Horizon staking * @dev In order to preserve storage compatibility some data structures keep deprecated fields. * These structures have then two representations, an internal one used by the contract storage and a public one. * Getter functions should retrieve internal representations, remove deprecated fields and return the public representation. diff --git a/packages/interfaces/contracts/horizon/internal/ILinkedList.sol b/packages/interfaces/contracts/horizon/internal/ILinkedList.sol index 4a993df29..4c099e65c 100644 --- a/packages/interfaces/contracts/horizon/internal/ILinkedList.sol +++ b/packages/interfaces/contracts/horizon/internal/ILinkedList.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Interface for the {LinkedList} library contract. + * @author Edge & Node + * @notice Interface for managing linked list data structures * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol b/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol new file mode 100644 index 000000000..4b27eaf39 --- /dev/null +++ b/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocationDistribution.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6 || ^0.8.0; +pragma abicoder v2; + +import { TargetIssuancePerBlock } from "./IIssuanceAllocatorTypes.sol"; + +/** + * @title IIssuanceAllocationDistribution + * @author Edge & Node + * @notice Interface for distribution and target interaction with the issuance allocator. + * This is the minimal interface that targets need to interact with the allocator. + */ +interface IIssuanceAllocationDistribution { + /** + * @notice Distribute issuance to allocated non-self-minting targets. + * @return Block number that issuance has been distributed to. That will normally be the current block number, unless the contract is paused. + * + * @dev When the contract is paused, no issuance is distributed and lastIssuanceBlock is not updated. + * @dev This function is permissionless and can be called by anyone, including targets as part of their normal flow. + */ + function distributeIssuance() external returns (uint256); + + /** + * @notice Target issuance per block information + * @param target Address of the target + * @return TargetIssuancePerBlock struct containing allocatorIssuanceBlockAppliedTo, selfIssuanceBlockAppliedTo, allocatorIssuancePerBlock, and selfIssuancePerBlock + * @dev This function does not revert when paused, instead the caller is expected to correctly read and apply the information provided. + * @dev Targets should check allocatorIssuanceBlockAppliedTo and selfIssuanceBlockAppliedTo - if either is not the current block, that type of issuance is paused for that target. + * @dev Targets should not check the allocator's pause state directly, but rely on the blockAppliedTo fields to determine if issuance is paused. + */ + function getTargetIssuancePerBlock(address target) external view returns (TargetIssuancePerBlock memory); +} diff --git a/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol b/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol new file mode 100644 index 000000000..b4a5d33a7 --- /dev/null +++ b/packages/interfaces/contracts/issuance/allocate/IIssuanceAllocatorTypes.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6 || ^0.8.0; +pragma abicoder v2; + +/** + * @notice Target issuance per block information + * @param allocatorIssuancePerBlock Issuance per block for allocator-minting (non-self-minting) + * @param allocatorIssuanceBlockAppliedTo The block up to which allocator issuance has been applied + * @param selfIssuancePerBlock Issuance per block for self-minting + * @param selfIssuanceBlockAppliedTo The block up to which self issuance has been applied + */ +struct TargetIssuancePerBlock { + uint256 allocatorIssuancePerBlock; + uint256 allocatorIssuanceBlockAppliedTo; + uint256 selfIssuancePerBlock; + uint256 selfIssuanceBlockAppliedTo; +} diff --git a/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol b/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol new file mode 100644 index 000000000..3fe539b95 --- /dev/null +++ b/packages/interfaces/contracts/issuance/allocate/IIssuanceTarget.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6 || ^0.8.0; + +/** + * @title IIssuanceTarget + * @author Edge & Node + * @notice Interface for contracts that receive issuance from an issuance allocator + */ +interface IIssuanceTarget { + /** + * @notice Called by the issuance allocator before the target's issuance allocation changes + * @dev The target should ensure that all issuance related calculations are up-to-date + * with the current block so that an allocation change can be applied correctly. + * Note that the allocation could change multiple times in the same block after + * this function has been called, only the final allocation is relevant. + */ + function beforeIssuanceAllocationChange() external; + + /** + * @notice Sets the issuance allocator for this target + * @dev This function facilitates upgrades by providing a standard way for targets + * to change their allocator. Implementations can define their own access control. + * @param newIssuanceAllocator Address of the issuance allocator + */ + function setIssuanceAllocator(address newIssuanceAllocator) external; +} diff --git a/packages/interfaces/contracts/issuance/common/IPausableControl.sol b/packages/interfaces/contracts/issuance/common/IPausableControl.sol new file mode 100644 index 000000000..83cfbc364 --- /dev/null +++ b/packages/interfaces/contracts/issuance/common/IPausableControl.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6 || ^0.8.0; + +/** + * @title IPausableControl + * @author Edge & Node + * @notice Interface for contracts that support pause/unpause functionality + * @dev This interface extends standard pausable functionality with explicit + * pause and unpause functions. Contracts implementing this interface allow + * authorized accounts to pause and unpause contract operations. + * Events (Paused, Unpaused) are inherited from OpenZeppelin's PausableUpgradeable. + */ +interface IPausableControl { + /** + * @notice Pause the contract + * @dev Pauses contract operations. Only functions using whenNotPaused + * modifier will be affected. + */ + function pause() external; + + /** + * @notice Unpause the contract + * @dev Resumes contract operations. Only functions using whenPaused + * modifier will be affected. + */ + function unpause() external; + + /** + * @notice Check if the contract is currently paused + * @return True if the contract is paused, false otherwise + */ + function paused() external view returns (bool); +} diff --git a/packages/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol b/packages/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol new file mode 100644 index 000000000..53c8acf85 --- /dev/null +++ b/packages/interfaces/contracts/issuance/eligibility/IRewardsEligibility.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6 || ^0.8.0; + +/** + * @title IRewardsEligibility + * @author Edge & Node + * @notice Minimal interface for checking indexer rewards eligibility + * @dev This is the interface that consumers (e.g., RewardsManager) need to check + * if an indexer is eligible to receive rewards + */ +interface IRewardsEligibility { + /** + * @notice Check if an indexer is eligible to receive rewards + * @param indexer Address of the indexer + * @return True if the indexer is eligible to receive rewards, false otherwise + */ + function isEligible(address indexer) external view returns (bool); +} diff --git a/packages/interfaces/contracts/subgraph-service/IDisputeManager.sol b/packages/interfaces/contracts/subgraph-service/IDisputeManager.sol index e82c96fa9..da1324cc9 100644 --- a/packages/interfaces/contracts/subgraph-service/IDisputeManager.sol +++ b/packages/interfaces/contracts/subgraph-service/IDisputeManager.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IAttestation } from "./internal/IAttestation.sol"; /** * @title IDisputeManager + * @author Edge & Node * @notice Interface for the {Dispute Manager} contract. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. @@ -90,7 +94,7 @@ interface IDisputeManager { event SubgraphServiceSet(address indexed subgraphService); /** - * @dev Emitted when a query dispute is created for `subgraphDeploymentId` and `indexer` + * @notice Emitted when a query dispute is created for `subgraphDeploymentId` and `indexer` * by `fisherman`. * The event emits the amount of `tokens` deposited by the fisherman and `attestation` submitted. * @param disputeId The dispute id @@ -99,8 +103,8 @@ interface IDisputeManager { * @param tokens The amount of tokens deposited by the fisherman * @param subgraphDeploymentId The subgraph deployment id * @param attestation The attestation - * @param cancellableAt The timestamp when the dispute can be cancelled * @param stakeSnapshot The stake snapshot of the indexer at the time of the dispute + * @param cancellableAt The timestamp when the dispute can be cancelled */ event QueryDisputeCreated( bytes32 indexed disputeId, @@ -114,7 +118,7 @@ interface IDisputeManager { ); /** - * @dev Emitted when an indexing dispute is created for `allocationId` and `indexer` + * @notice Emitted when an indexing dispute is created for `allocationId` and `indexer` * by `fisherman`. * The event emits the amount of `tokens` deposited by the fisherman. * @param disputeId The dispute id @@ -140,7 +144,7 @@ interface IDisputeManager { ); /** - * @dev Emitted when a legacy dispute is created for `allocationId` and `fisherman`. + * @notice Emitted when a legacy dispute is created for `allocationId` and `fisherman`. * The event emits the amount of `tokensSlash` to slash and `tokensRewards` to reward the fisherman. * @param disputeId The dispute id * @param indexer The indexer address @@ -159,7 +163,7 @@ interface IDisputeManager { ); /** - * @dev Emitted when arbitrator accepts a `disputeId` to `indexer` created by `fisherman`. + * @notice Emitted when arbitrator accepts a `disputeId` to `indexer` created by `fisherman`. * The event emits the amount `tokens` transferred to the fisherman, the deposit plus reward. * @param disputeId The dispute id * @param indexer The indexer address @@ -174,7 +178,7 @@ interface IDisputeManager { ); /** - * @dev Emitted when arbitrator rejects a `disputeId` for `indexer` created by `fisherman`. + * @notice Emitted when arbitrator rejects a `disputeId` for `indexer` created by `fisherman`. * The event emits the amount `tokens` burned from the fisherman deposit. * @param disputeId The dispute id * @param indexer The indexer address @@ -189,7 +193,7 @@ interface IDisputeManager { ); /** - * @dev Emitted when arbitrator draw a `disputeId` for `indexer` created by `fisherman`. + * @notice Emitted when arbitrator draw a `disputeId` for `indexer` created by `fisherman`. * The event emits the amount `tokens` used as deposit and returned to the fisherman. * @param disputeId The dispute id * @param indexer The indexer address @@ -199,7 +203,7 @@ interface IDisputeManager { event DisputeDrawn(bytes32 indexed disputeId, address indexed indexer, address indexed fisherman, uint256 tokens); /** - * @dev Emitted when two disputes are in conflict to link them. + * @notice Emitted when two disputes are in conflict to link them. * This event will be emitted after each DisputeCreated event is emitted * for each of the individual disputes. * @param disputeId1 The first dispute id @@ -208,7 +212,7 @@ interface IDisputeManager { event DisputeLinked(bytes32 indexed disputeId1, bytes32 indexed disputeId2); /** - * @dev Emitted when a dispute is cancelled by the fisherman. + * @notice Emitted when a dispute is cancelled by the fisherman. * The event emits the amount `tokens` returned to the fisherman. * @param disputeId The dispute id * @param indexer The indexer address diff --git a/packages/interfaces/contracts/subgraph-service/ISubgraphService.sol b/packages/interfaces/contracts/subgraph-service/ISubgraphService.sol index 88e48d6d4..5b084c7a7 100644 --- a/packages/interfaces/contracts/subgraph-service/ISubgraphService.sol +++ b/packages/interfaces/contracts/subgraph-service/ISubgraphService.sol @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IDataServiceFees } from "../data-service/IDataServiceFees.sol"; import { IGraphPayments } from "../horizon/IGraphPayments.sol"; @@ -9,6 +12,7 @@ import { ILegacyAllocation } from "./internal/ILegacyAllocation.sol"; /** * @title Interface for the {SubgraphService} contract + * @author Edge & Node * @dev This interface extends {IDataServiceFees} and {IDataService}. * @notice The Subgraph Service is a data service built on top of Graph Horizon that supports the use case of * subgraph indexing and querying. The {SubgraphService} contract implements the flows described in the Data diff --git a/packages/interfaces/contracts/subgraph-service/internal/IAllocation.sol b/packages/interfaces/contracts/subgraph-service/internal/IAllocation.sol index 97b4f1176..a81e734d7 100644 --- a/packages/interfaces/contracts/subgraph-service/internal/IAllocation.sol +++ b/packages/interfaces/contracts/subgraph-service/internal/IAllocation.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Interface for the {Allocation} library contract. + * @author Edge & Node + * @notice Interface for managing allocation data and operations * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/interfaces/contracts/subgraph-service/internal/IAttestation.sol b/packages/interfaces/contracts/subgraph-service/internal/IAttestation.sol index e425a917a..fcee6d692 100644 --- a/packages/interfaces/contracts/subgraph-service/internal/IAttestation.sol +++ b/packages/interfaces/contracts/subgraph-service/internal/IAttestation.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Interface for the {Attestation} library contract. + * @author Edge & Node + * @notice Interface for managing attestation data and verification * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/interfaces/contracts/subgraph-service/internal/ILegacyAllocation.sol b/packages/interfaces/contracts/subgraph-service/internal/ILegacyAllocation.sol index 74405a439..c5bf7f8c7 100644 --- a/packages/interfaces/contracts/subgraph-service/internal/ILegacyAllocation.sol +++ b/packages/interfaces/contracts/subgraph-service/internal/ILegacyAllocation.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; /** * @title Interface for the {LegacyAllocation} library contract. + * @author Edge & Node + * @notice Interface for managing legacy allocation data * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/interfaces/contracts/token-distribution/IGraphTokenLockWallet.sol b/packages/interfaces/contracts/token-distribution/IGraphTokenLockWallet.sol index 735d35046..06e38dc20 100644 --- a/packages/interfaces/contracts/token-distribution/IGraphTokenLockWallet.sol +++ b/packages/interfaces/contracts/token-distribution/IGraphTokenLockWallet.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events /** * @title IGraphTokenLockWallet + * @author Edge & Node * @notice Interface for the GraphTokenLockWallet contract that manages locked tokens with vesting schedules * @dev This interface includes core vesting functionality. Protocol interaction functions are in IGraphTokenLockWalletToolshed */ @@ -17,50 +21,157 @@ interface IGraphTokenLockWallet { } // Events + + /// @notice Emitted when the manager is updated + /// @param _oldManager The previous manager address + /// @param _newManager The new manager address event ManagerUpdated(address indexed _oldManager, address indexed _newManager); + + /// @notice Emitted when ownership is transferred + /// @param previousOwner The previous owner address + /// @param newOwner The new owner address event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /// @notice Emitted when token destinations are approved event TokenDestinationsApproved(); + + /// @notice Emitted when token destinations are revoked event TokenDestinationsRevoked(); + + /// @notice Emitted when tokens are released to beneficiary + /// @param beneficiary The beneficiary address + /// @param amount The amount of tokens released event TokensReleased(address indexed beneficiary, uint256 amount); + + /// @notice Emitted when tokens are revoked + /// @param beneficiary The beneficiary address + /// @param amount The amount of tokens revoked event TokensRevoked(address indexed beneficiary, uint256 amount); + + /// @notice Emitted when tokens are withdrawn + /// @param beneficiary The beneficiary address + /// @param amount The amount of tokens withdrawn event TokensWithdrawn(address indexed beneficiary, uint256 amount); // View functions - Vesting Details + + /// @notice Get the beneficiary address + /// @return The beneficiary address function beneficiary() external view returns (address); + + /// @notice Get the token contract address + /// @return The token contract address function token() external view returns (address); + + /// @notice Get the total amount of tokens managed by this contract + /// @return The managed token amount function managedAmount() external view returns (uint256); + + /// @notice Get the vesting start time + /// @return The start time timestamp function startTime() external view returns (uint256); + + /// @notice Get the vesting end time + /// @return The end time timestamp function endTime() external view returns (uint256); + + /// @notice Get the number of vesting periods + /// @return The number of periods function periods() external view returns (uint256); + + /// @notice Get the release start time + /// @return The release start time timestamp function releaseStartTime() external view returns (uint256); + + /// @notice Get the vesting cliff time + /// @return The cliff time timestamp function vestingCliffTime() external view returns (uint256); + + /// @notice Get the revocability status + /// @return The revocability status function revocable() external view returns (Revocability); + + /// @notice Check if the vesting has been revoked + /// @return True if revoked, false otherwise function isRevoked() external view returns (bool); // View functions - Vesting Calculations + + /// @notice Get the current timestamp + /// @return The current timestamp function currentTime() external view returns (uint256); + + /// @notice Get the total vesting duration + /// @return The duration in seconds function duration() external view returns (uint256); + + /// @notice Get the time elapsed since vesting start + /// @return The elapsed time in seconds function sinceStartTime() external view returns (uint256); + + /// @notice Get the amount of tokens released per period + /// @return The amount per period function amountPerPeriod() external view returns (uint256); + + /// @notice Get the duration of each vesting period + /// @return The period duration in seconds function periodDuration() external view returns (uint256); + + /// @notice Get the current vesting period + /// @return The current period number function currentPeriod() external view returns (uint256); + + /// @notice Get the number of periods that have passed + /// @return The number of passed periods function passedPeriods() external view returns (uint256); // View functions - Token Amounts + + /// @notice Get the amount of tokens that can be released + /// @return The releasable token amount function releasableAmount() external view returns (uint256); + + /// @notice Get the amount of tokens that have vested + /// @return The vested token amount function vestedAmount() external view returns (uint256); + + /// @notice Get the amount of tokens that have been released + /// @return The released token amount function releasedAmount() external view returns (uint256); + + /// @notice Get the amount of tokens that have been used + /// @return The used token amount function usedAmount() external view returns (uint256); + + /// @notice Get the current token balance of the contract + /// @return The current balance function currentBalance() external view returns (uint256); + + /// @notice Get the surplus amount of tokens + /// @return The surplus token amount function surplusAmount() external view returns (uint256); + + /// @notice Get the total outstanding token amount + /// @return The total outstanding amount function totalOutstandingAmount() external view returns (uint256); // State-changing functions + + /// @notice Release vested tokens to the beneficiary function release() external; + + /// @notice Withdraw surplus tokens + /// @param _amount The amount of surplus tokens to withdraw function withdrawSurplus(uint256 _amount) external; + + /// @notice Approve protocol interactions function approveProtocol() external; + + /// @notice Revoke protocol interactions function revokeProtocol() external; // Fallback for forwarding calls + + /// @notice Fallback function for forwarding calls fallback() external payable; } diff --git a/packages/interfaces/contracts/toolshed/IControllerToolshed.sol b/packages/interfaces/contracts/toolshed/IControllerToolshed.sol index e4dbd8d41..8b00216f7 100644 --- a/packages/interfaces/contracts/toolshed/IControllerToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IControllerToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { IController } from "../contracts/governance/IController.sol"; import { IGoverned } from "../contracts/governance/IGoverned.sol"; diff --git a/packages/interfaces/contracts/toolshed/IDisputeManagerToolshed.sol b/packages/interfaces/contracts/toolshed/IDisputeManagerToolshed.sol index 0496d3326..8c3e4390e 100644 --- a/packages/interfaces/contracts/toolshed/IDisputeManagerToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IDisputeManagerToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec import { IDisputeManager } from "../subgraph-service/IDisputeManager.sol"; import { IOwnable } from "./internal/IOwnable.sol"; diff --git a/packages/interfaces/contracts/toolshed/IEpochManagerToolshed.sol b/packages/interfaces/contracts/toolshed/IEpochManagerToolshed.sol index 427df1f64..649ae7150 100644 --- a/packages/interfaces/contracts/toolshed/IEpochManagerToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IEpochManagerToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { IEpochManager } from "../contracts/epochs/IEpochManager.sol"; diff --git a/packages/interfaces/contracts/toolshed/IGraphTallyCollectorToolshed.sol b/packages/interfaces/contracts/toolshed/IGraphTallyCollectorToolshed.sol index f6be272e4..100e83ff1 100644 --- a/packages/interfaces/contracts/toolshed/IGraphTallyCollectorToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IGraphTallyCollectorToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec import { IGraphTallyCollector } from "../horizon/IGraphTallyCollector.sol"; diff --git a/packages/interfaces/contracts/toolshed/IGraphTokenLockWalletToolshed.sol b/packages/interfaces/contracts/toolshed/IGraphTokenLockWalletToolshed.sol index d442f0c0f..b412f6eb7 100644 --- a/packages/interfaces/contracts/toolshed/IGraphTokenLockWalletToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IGraphTokenLockWalletToolshed.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { IGraphTokenLockWallet } from "../token-distribution/IGraphTokenLockWallet.sol"; import { IGraphPayments } from "../horizon/IGraphPayments.sol"; /** * @title IGraphTokenLockWalletToolshed + * @author Edge & Node * @notice Extended interface for GraphTokenLockWallet that includes Horizon protocol interaction functions * @dev Functions included are based on the GraphTokenLockManager whitelist for vesting contracts on Horizon */ diff --git a/packages/interfaces/contracts/toolshed/IHorizonStakingToolshed.sol b/packages/interfaces/contracts/toolshed/IHorizonStakingToolshed.sol index b021d3af9..59e549998 100644 --- a/packages/interfaces/contracts/toolshed/IHorizonStakingToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IHorizonStakingToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { IHorizonStaking } from "../horizon/IHorizonStaking.sol"; import { IMulticall } from "../contracts/base/IMulticall.sol"; diff --git a/packages/interfaces/contracts/toolshed/IL2CurationToolshed.sol b/packages/interfaces/contracts/toolshed/IL2CurationToolshed.sol index 9b4b81782..aae712596 100644 --- a/packages/interfaces/contracts/toolshed/IL2CurationToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IL2CurationToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { ICuration } from "../contracts/curation/ICuration.sol"; import { IL2Curation } from "../contracts/l2/curation/IL2Curation.sol"; diff --git a/packages/interfaces/contracts/toolshed/IL2GNSToolshed.sol b/packages/interfaces/contracts/toolshed/IL2GNSToolshed.sol index 7cfb3a579..c672be2c8 100644 --- a/packages/interfaces/contracts/toolshed/IL2GNSToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IL2GNSToolshed.sol @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; +pragma abicoder v2; + +// solhint-disable use-natspec import { IGNS } from "../contracts/discovery/IGNS.sol"; import { IL2GNS } from "../contracts/l2/discovery/IL2GNS.sol"; diff --git a/packages/interfaces/contracts/toolshed/IPaymentsEscrowToolshed.sol b/packages/interfaces/contracts/toolshed/IPaymentsEscrowToolshed.sol index e77a0a1b0..c7b9b81f2 100644 --- a/packages/interfaces/contracts/toolshed/IPaymentsEscrowToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IPaymentsEscrowToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec import { IPaymentsEscrow } from "../horizon/IPaymentsEscrow.sol"; diff --git a/packages/interfaces/contracts/toolshed/IRewardsManagerToolshed.sol b/packages/interfaces/contracts/toolshed/IRewardsManagerToolshed.sol index 9fd0b6e1b..03b584ba4 100644 --- a/packages/interfaces/contracts/toolshed/IRewardsManagerToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IRewardsManagerToolshed.sol @@ -1,5 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events import { IRewardsManager } from "../contracts/rewards/IRewardsManager.sol"; @@ -10,23 +15,23 @@ interface IRewardsManagerToolshed is IRewardsManager { event RewardsAssigned(address indexed indexer, address indexed allocationID, uint256 amount); /** - * @dev Emitted when rewards are assigned to an indexer. + * @notice Emitted when rewards are assigned to an indexer (Horizon version) * @dev We use the Horizon prefix to change the event signature which makes network subgraph development much easier */ event HorizonRewardsAssigned(address indexed indexer, address indexed allocationID, uint256 amount); /** - * @dev Emitted when rewards are denied to an indexer. + * @notice Emitted when rewards are denied to an indexer */ event RewardsDenied(address indexed indexer, address indexed allocationID); /** - * @dev Emitted when a subgraph is denied for claiming rewards. + * @notice Emitted when a subgraph is denied for claiming rewards */ event RewardsDenylistUpdated(bytes32 indexed subgraphDeploymentID, uint256 sinceBlock); /** - * @dev Emitted when the subgraph service is set + * @notice Emitted when the subgraph service is set */ event SubgraphServiceSet(address indexed oldSubgraphService, address indexed newSubgraphService); diff --git a/packages/interfaces/contracts/toolshed/IServiceRegistryToolshed.sol b/packages/interfaces/contracts/toolshed/IServiceRegistryToolshed.sol index 59155f1f7..46b644418 100644 --- a/packages/interfaces/contracts/toolshed/IServiceRegistryToolshed.sol +++ b/packages/interfaces/contracts/toolshed/IServiceRegistryToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.7.6 || 0.8.27; +pragma solidity ^0.7.6 || ^0.8.0; + +// solhint-disable use-natspec import { IServiceRegistry } from "../contracts/discovery/IServiceRegistry.sol"; diff --git a/packages/interfaces/contracts/toolshed/ISubgraphServiceToolshed.sol b/packages/interfaces/contracts/toolshed/ISubgraphServiceToolshed.sol index 6d684c7ba..a7a05fbcd 100644 --- a/packages/interfaces/contracts/toolshed/ISubgraphServiceToolshed.sol +++ b/packages/interfaces/contracts/toolshed/ISubgraphServiceToolshed.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec import { ISubgraphService } from "../subgraph-service/ISubgraphService.sol"; import { IOwnable } from "./internal/IOwnable.sol"; diff --git a/packages/interfaces/contracts/toolshed/internal/IAllocationManager.sol b/packages/interfaces/contracts/toolshed/internal/IAllocationManager.sol index d00b9a9f4..9e6e8b704 100644 --- a/packages/interfaces/contracts/toolshed/internal/IAllocationManager.sol +++ b/packages/interfaces/contracts/toolshed/internal/IAllocationManager.sol @@ -1,5 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events interface IAllocationManager { // Events diff --git a/packages/interfaces/contracts/toolshed/internal/IOwnable.sol b/packages/interfaces/contracts/toolshed/internal/IOwnable.sol index d5b894c58..00628a696 100644 --- a/packages/interfaces/contracts/toolshed/internal/IOwnable.sol +++ b/packages/interfaces/contracts/toolshed/internal/IOwnable.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.27; +// solhint-disable use-natspec + +pragma solidity ^0.8.22; /// @title IOwnable /// @notice Interface for Ownable contracts diff --git a/packages/interfaces/contracts/toolshed/internal/IPausable.sol b/packages/interfaces/contracts/toolshed/internal/IPausable.sol index aee534ce3..456dfb534 100644 --- a/packages/interfaces/contracts/toolshed/internal/IPausable.sol +++ b/packages/interfaces/contracts/toolshed/internal/IPausable.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec /// @title IPausable /// @notice Interface for Pausable contract diff --git a/packages/interfaces/contracts/toolshed/internal/IProvisionManager.sol b/packages/interfaces/contracts/toolshed/internal/IProvisionManager.sol index 4a536e5e1..26cafdc31 100644 --- a/packages/interfaces/contracts/toolshed/internal/IProvisionManager.sol +++ b/packages/interfaces/contracts/toolshed/internal/IProvisionManager.sol @@ -1,5 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.22; + +// solhint-disable use-natspec + +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events interface IProvisionManager { // Events diff --git a/packages/interfaces/contracts/toolshed/internal/IProvisionTracker.sol b/packages/interfaces/contracts/toolshed/internal/IProvisionTracker.sol index f474be14e..b0a1108a3 100644 --- a/packages/interfaces/contracts/toolshed/internal/IProvisionTracker.sol +++ b/packages/interfaces/contracts/toolshed/internal/IProvisionTracker.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.27; +// solhint-disable use-natspec + +pragma solidity ^0.8.22; interface IProvisionTracker { // Errors diff --git a/packages/issuance/.markdownlint.json b/packages/issuance/.markdownlint.json new file mode 100644 index 000000000..18947b0be --- /dev/null +++ b/packages/issuance/.markdownlint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../.markdownlint.json" +} diff --git a/packages/issuance/.solcover.js b/packages/issuance/.solcover.js new file mode 100644 index 000000000..d8bbec4bb --- /dev/null +++ b/packages/issuance/.solcover.js @@ -0,0 +1,15 @@ +module.exports = { + skipFiles: ['test/'], + providerOptions: { + mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect', + network_id: 1337, + }, + // Use default istanbulFolder: './coverage' + // Exclude 'html' to avoid duplicate HTML files (lcov already generates HTML in lcov-report/) + istanbulReporter: ['lcov', 'text', 'json'], + configureYulOptimizer: true, + mocha: { + grep: '@skip-on-coverage', + invert: true, + }, +} diff --git a/packages/issuance/.solhint.json b/packages/issuance/.solhint.json new file mode 100644 index 000000000..d30847305 --- /dev/null +++ b/packages/issuance/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": ["solhint:recommended", "./../../.solhint.json"] +} diff --git a/packages/issuance/README.md b/packages/issuance/README.md new file mode 100644 index 000000000..16e2520b6 --- /dev/null +++ b/packages/issuance/README.md @@ -0,0 +1,62 @@ +# The Graph Issuance Contracts + +This package contains smart contracts for The Graph's issuance functionality. + +## Overview + +The issuance contracts handle token issuance mechanisms for The Graph protocol. + +### Contracts + +- **[IssuanceAllocator](contracts/allocate/IssuanceAllocator.md)** - Central distribution hub for token issuance, allocating tokens to different protocol components based on configured proportions +- **[RewardsEligibilityOracle](contracts/eligibility/RewardsEligibilityOracle.md)** - Oracle-based eligibility system for indexer rewards with time-based expiration +- **DirectAllocation** - Simple target contract for receiving and distributing allocated tokens + +## Development + +### Setup + +```bash +# Install dependencies +pnpm install + +# Build +pnpm build + +# Test +pnpm test +``` + +### Testing + +To run the tests: + +```bash +pnpm test +``` + +For coverage: + +```bash +pnpm test:coverage +``` + +### Linting + +To lint the contracts and tests: + +```bash +pnpm lint +``` + +### Contract Size + +To check contract sizes: + +```bash +pnpm size +``` + +## License + +GPL-2.0-or-later diff --git a/packages/issuance/contracts/common/BaseUpgradeable.sol b/packages/issuance/contracts/common/BaseUpgradeable.sol new file mode 100644 index 000000000..ead4f6a4f --- /dev/null +++ b/packages/issuance/contracts/common/BaseUpgradeable.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity 0.8.27; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; +import { IPausableControl } from "@graphprotocol/interfaces/contracts/issuance/common/IPausableControl.sol"; + +/** + * @title BaseUpgradeable + * @author Edge & Node + * @notice A base contract that provides role-based access control and pausability. + * + * @dev This contract combines OpenZeppelin's AccessControl and Pausable + * to provide a standardized way to manage access control and pausing functionality. + * It uses ERC-7201 namespaced storage pattern for better storage isolation. + * This contract is abstract and meant to be inherited by other contracts. + * @custom:security-contact Please email security+contracts@thegraph.com if you find any bugs. We might have an active bug bounty program. + */ +abstract contract BaseUpgradeable is Initializable, AccessControlUpgradeable, PausableUpgradeable, IPausableControl { + // -- Constants -- + + /// @notice One million - used as the denominator for values provided as Parts Per Million (PPM) + /// @dev This constant represents 1,000,000 and serves as the denominator when working with + /// PPM values. For example, 50% would be represented as 500,000 PPM, calculated as + /// (500,000 / MILLION) = 0.5 = 50% + uint256 public constant MILLION = 1_000_000; + + // -- Role Constants -- + + /** + * @notice Role identifier for governor accounts + * @dev Governors have the highest level of access and can: + * - Grant and revoke roles within the established hierarchy + * - Perform administrative functions and system configuration + * - Set critical parameters and upgrade contracts + * Admin of: GOVERNOR_ROLE, PAUSE_ROLE, OPERATOR_ROLE + */ + bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); + + /** + * @notice Role identifier for pause accounts + * @dev Pause role holders can: + * - Pause and unpause contract operations for emergency situations + * Typically granted to automated monitoring systems or emergency responders. + * Pausing is intended for quick response to potential threats, and giving time for investigation and resolution (potentially with governance intervention). + * Admin: GOVERNOR_ROLE + */ + bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE"); + + /** + * @notice Role identifier for operator accounts + * @dev Operators can: + * - Perform operational tasks as defined by inheriting contracts + * - Manage roles that are designated as operator-administered + * Admin: GOVERNOR_ROLE + */ + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + + // -- Immutable Variables -- + + /// @notice The Graph Token contract + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + IGraphToken internal immutable GRAPH_TOKEN; + + // -- Custom Errors -- + + /// @notice Thrown when attempting to set the Graph Token to the zero address + error GraphTokenCannotBeZeroAddress(); + + /// @notice Thrown when attempting to set the governor to the zero address + error GovernorCannotBeZeroAddress(); + + // -- Constructor -- + + /** + * @notice Constructor for the BaseUpgradeable contract + * @dev This contract is upgradeable, but we use the constructor to set immutable variables + * and disable initializers to prevent the implementation contract from being initialized. + * @param graphToken Address of the Graph Token contract + * @custom:oz-upgrades-unsafe-allow constructor + */ + constructor(address graphToken) { + require(graphToken != address(0), GraphTokenCannotBeZeroAddress()); + GRAPH_TOKEN = IGraphToken(graphToken); + _disableInitializers(); + } + + // -- Initialization -- + + /** + * @notice Internal function to initialize the BaseUpgradeable contract + * @dev This function is used by child contracts to initialize the BaseUpgradeable contract + * @param governor Address that will have the GOVERNOR_ROLE + */ + function __BaseUpgradeable_init(address governor) internal { + // solhint-disable-previous-line func-name-mixedcase + + __AccessControl_init(); + __Pausable_init(); + + __BaseUpgradeable_init_unchained(governor); + } + + /** + * @notice Internal unchained initialization function for BaseUpgradeable + * @dev This function sets up the governor role and role admin hierarchy + * @param governor Address that will have the GOVERNOR_ROLE + */ + function __BaseUpgradeable_init_unchained(address governor) internal { + // solhint-disable-previous-line func-name-mixedcase + + require(governor != address(0), GovernorCannotBeZeroAddress()); + + // Set up role admin hierarchy: + // GOVERNOR is admin of GOVERNOR, PAUSE, and OPERATOR roles + _setRoleAdmin(GOVERNOR_ROLE, GOVERNOR_ROLE); + _setRoleAdmin(PAUSE_ROLE, GOVERNOR_ROLE); + _setRoleAdmin(OPERATOR_ROLE, GOVERNOR_ROLE); + + // Grant initial governor role + _grantRole(GOVERNOR_ROLE, governor); + } + + // -- External Functions -- + + /** + * @inheritdoc IPausableControl + */ + function pause() external override onlyRole(PAUSE_ROLE) { + _pause(); + } + + /** + * @inheritdoc IPausableControl + */ + function unpause() external override onlyRole(PAUSE_ROLE) { + _unpause(); + } + + /** + * @inheritdoc IPausableControl + */ + function paused() public view virtual override(PausableUpgradeable, IPausableControl) returns (bool) { + return super.paused(); + } + + /** + * @notice Check if this contract supports a given interface + * @dev Adds support for IPausableControl interface + * @param interfaceId The interface identifier to check + * @return True if the contract supports the interface, false otherwise + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IPausableControl).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/packages/issuance/hardhat.base.config.ts b/packages/issuance/hardhat.base.config.ts new file mode 100644 index 000000000..e4d0cc8bb --- /dev/null +++ b/packages/issuance/hardhat.base.config.ts @@ -0,0 +1,24 @@ +import { hardhatBaseConfig } from '@graphprotocol/toolshed/hardhat' +import type { HardhatUserConfig } from 'hardhat/config' + +// Issuance-specific Solidity configuration with Cancun EVM version +// Based on toolshed solidityUserConfig but with Cancun EVM target +export const issuanceSolidityConfig = { + version: '0.8.27', + settings: { + optimizer: { + enabled: true, + runs: 100, + }, + evmVersion: 'cancun' as const, + }, +} + +// Base configuration for issuance package - inherits from toolshed and overrides Solidity config +export const issuanceBaseConfig = (() => { + const baseConfig = hardhatBaseConfig(require) + return { + ...baseConfig, + solidity: issuanceSolidityConfig, + } as HardhatUserConfig +})() diff --git a/packages/issuance/hardhat.config.ts b/packages/issuance/hardhat.config.ts new file mode 100644 index 000000000..f76949af8 --- /dev/null +++ b/packages/issuance/hardhat.config.ts @@ -0,0 +1,26 @@ +import '@nomicfoundation/hardhat-ethers' +import '@typechain/hardhat' +import 'hardhat-contract-sizer' +import '@openzeppelin/hardhat-upgrades' +import '@nomicfoundation/hardhat-verify' + +import type { HardhatUserConfig } from 'hardhat/config' + +import { issuanceBaseConfig } from './hardhat.base.config' + +const config: HardhatUserConfig = { + ...issuanceBaseConfig, + // Main config specific settings + typechain: { + outDir: 'types', + target: 'ethers-v6', + }, + paths: { + sources: './contracts', + tests: './test/tests', + artifacts: './artifacts', + cache: './cache', + }, +} + +export default config diff --git a/packages/issuance/hardhat.coverage.config.ts b/packages/issuance/hardhat.coverage.config.ts new file mode 100644 index 000000000..01ee96e83 --- /dev/null +++ b/packages/issuance/hardhat.coverage.config.ts @@ -0,0 +1,22 @@ +import '@nomicfoundation/hardhat-ethers' +import '@nomicfoundation/hardhat-chai-matchers' +import '@nomicfoundation/hardhat-network-helpers' +import '@openzeppelin/hardhat-upgrades' +import 'hardhat-gas-reporter' +import 'solidity-coverage' + +import { HardhatUserConfig } from 'hardhat/config' + +import { issuanceBaseConfig } from './hardhat.base.config' + +const config: HardhatUserConfig = { + ...issuanceBaseConfig, + paths: { + sources: './contracts', + tests: './test/tests', + artifacts: './coverage/artifacts', + cache: './coverage/cache', + }, +} as HardhatUserConfig + +export default config diff --git a/packages/issuance/package.json b/packages/issuance/package.json new file mode 100644 index 000000000..fbb658193 --- /dev/null +++ b/packages/issuance/package.json @@ -0,0 +1,79 @@ +{ + "name": "@graphprotocol/issuance", + "version": "1.0.0", + "publishConfig": { + "access": "public" + }, + "description": "The Graph Issuance Contracts", + "author": "Edge & Node", + "license": "GPL-2.0-or-later", + "main": "index.js", + "exports": { + ".": "./index.js", + "./artifacts/*": "./artifacts/*", + "./contracts/*": "./contracts/*", + "./types": "./types/index.ts", + "./types/*": "./types/*" + }, + "scripts": { + "build": "pnpm build:dep && pnpm build:self", + "build:dep": "pnpm --filter '@graphprotocol/issuance^...' run build:self", + "build:self": "pnpm compile && pnpm build:self:typechain", + "build:coverage": "pnpm build:dep && pnpm build:self:coverage", + "build:self:coverage": "npx hardhat compile --config hardhat.coverage.config.ts && pnpm build:self:typechain", + "build:self:typechain": "bash -c 'missing=$(grep -rL \"static readonly interfaceId\" types/factories --include=\"*__factory.ts\" 2>/dev/null | wc -l); if [ $missing -gt 0 ]; then node -e \"require('\"'\"'@graphprotocol/interfaces/utils'\"'\"').addInterfaceIds('\"'\"'types/factories'\"'\"')\"; fi'", + "clean": "rm -rf artifacts/ types/ forge-artifacts/ cache_forge/ coverage/ cache/ .eslintcache", + "compile": "hardhat compile --quiet", + "test": "pnpm --filter @graphprotocol/issuance-test test", + "test:coverage": "pnpm --filter @graphprotocol/issuance-test run test:coverage", + "lint": "pnpm lint:ts; pnpm lint:sol; pnpm lint:md; pnpm lint:json", + "lint:ts": "eslint '**/*.{js,ts,cjs,mjs,jsx,tsx}' --fix --cache; prettier -w --cache --log-level warn '**/*.{js,ts,cjs,mjs,jsx,tsx}'", + "lint:sol": "solhint --fix --noPrompt --noPoster 'contracts/**/*.sol'; prettier -w --cache --log-level warn 'contracts/**/*.sol'", + "lint:md": "markdownlint --fix --ignore-path ../../.gitignore '**/*.md'; prettier -w --cache --log-level warn '**/*.md'", + "lint:json": "prettier -w --cache --log-level warn '**/*.json'", + "typechain": "hardhat typechain", + "verify": "hardhat verify", + "size": "hardhat size-contracts", + "forge:build": "forge build" + }, + "files": [ + "artifacts/**/*", + "types/**/*", + "contracts/**/*", + "README.md" + ], + "devDependencies": { + "@graphprotocol/interfaces": "workspace:^", + "@graphprotocol/toolshed": "workspace:^", + "@nomicfoundation/hardhat-ethers": "catalog:", + "@nomicfoundation/hardhat-verify": "catalog:", + "@openzeppelin/contracts": "^5.4.0", + "@openzeppelin/contracts-upgradeable": "^5.4.0", + "@openzeppelin/hardhat-upgrades": "^3.9.0", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "catalog:", + "@types/node": "^20.17.50", + "dotenv": "catalog:", + "eslint": "catalog:", + "ethers": "catalog:", + "glob": "catalog:", + "globals": "catalog:", + "hardhat": "catalog:", + "hardhat-contract-sizer": "catalog:", + "hardhat-secure-accounts": "catalog:", + "hardhat-storage-layout": "catalog:", + "lint-staged": "catalog:", + "markdownlint-cli": "catalog:", + "prettier": "catalog:", + "prettier-plugin-solidity": "catalog:", + "solhint": "catalog:", + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "catalog:", + "typescript-eslint": "catalog:", + "yaml-lint": "catalog:" + }, + "dependencies": { + "@noble/hashes": "^1.8.0" + } +} diff --git a/packages/issuance/prettier.config.cjs b/packages/issuance/prettier.config.cjs new file mode 100644 index 000000000..4e8dcf4f3 --- /dev/null +++ b/packages/issuance/prettier.config.cjs @@ -0,0 +1,5 @@ +const baseConfig = require('../../prettier.config.cjs') + +module.exports = { + ...baseConfig, +} diff --git a/packages/issuance/test/package.json b/packages/issuance/test/package.json new file mode 100644 index 000000000..f362b4c9b --- /dev/null +++ b/packages/issuance/test/package.json @@ -0,0 +1,62 @@ +{ + "name": "@graphprotocol/issuance-test", + "version": "1.0.0", + "private": true, + "description": "Test utilities for @graphprotocol/issuance", + "author": "Edge & Node", + "license": "GPL-2.0-or-later", + "main": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": { + "default": "./src/index.ts", + "types": "./src/index.ts" + } + }, + "scripts": { + "build": "pnpm build:dep && pnpm build:self", + "build:dep": "pnpm --filter '@graphprotocol/issuance-test^...' run build:self", + "build:self": "tsc --build", + "build:coverage": "pnpm build:dep:coverage && pnpm build:self", + "build:dep:coverage": "pnpm --filter '@graphprotocol/issuance-test^...' run build:coverage", + "clean": "rm -rf .eslintcache artifacts/", + "test": "pnpm build && pnpm test:self", + "test:self": "cd .. && hardhat test", + "test:coverage": "pnpm build:coverage && pnpm test:coverage:self", + "test:coverage:self": "cd .. && npx hardhat coverage --config hardhat.coverage.config.ts", + "lint": "pnpm lint:ts; pnpm lint:json", + "lint:ts": "eslint '**/*.{js,ts,cjs,mjs,jsx,tsx}' --fix --cache; prettier -w --cache --log-level warn '**/*.{js,ts,cjs,mjs,jsx,tsx}'", + "lint:json": "prettier -w --cache --log-level warn '**/*.json'" + }, + "dependencies": { + "@graphprotocol/issuance": "workspace:^", + "@graphprotocol/interfaces": "workspace:^", + "@graphprotocol/contracts": "workspace:^" + }, + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "catalog:", + "@nomicfoundation/hardhat-foundry": "^1.1.1", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "5.0.0", + "@openzeppelin/contracts": "^5.4.0", + "@openzeppelin/contracts-upgradeable": "^5.4.0", + "@openzeppelin/foundry-upgrades": "0.4.0", + "@types/chai": "^4.3.20", + "@types/mocha": "^10.0.10", + "@types/node": "^20.17.50", + "chai": "^4.3.7", + "dotenv": "^16.5.0", + "eslint": "catalog:", + "eslint-plugin-no-only-tests": "catalog:", + "ethers": "catalog:", + "forge-std": "https://github.com/foundry-rs/forge-std/tarball/v1.9.7", + "glob": "catalog:", + "hardhat": "catalog:", + "hardhat-gas-reporter": "catalog:", + "prettier": "catalog:", + "solidity-coverage": "^0.8.0", + "ts-node": "^10.9.2", + "typescript": "catalog:" + } +} diff --git a/packages/issuance/test/prettier.config.cjs b/packages/issuance/test/prettier.config.cjs new file mode 100644 index 000000000..8eb0a0bee --- /dev/null +++ b/packages/issuance/test/prettier.config.cjs @@ -0,0 +1,5 @@ +const baseConfig = require('../prettier.config.cjs') + +module.exports = { + ...baseConfig, +} diff --git a/packages/issuance/test/src/index.ts b/packages/issuance/test/src/index.ts new file mode 100644 index 000000000..614cfd50d --- /dev/null +++ b/packages/issuance/test/src/index.ts @@ -0,0 +1,5 @@ +// Test utilities for @graphprotocol/issuance +// This package contains test files, test helpers, and testing utilities + +// This package provides test utilities for issuance contracts +export const PACKAGE_NAME = '@graphprotocol/issuance-test' diff --git a/packages/issuance/test/tests/common/CommonInterfaceIdStability.test.ts b/packages/issuance/test/tests/common/CommonInterfaceIdStability.test.ts new file mode 100644 index 000000000..e91b12bd2 --- /dev/null +++ b/packages/issuance/test/tests/common/CommonInterfaceIdStability.test.ts @@ -0,0 +1,27 @@ +import { IPausableControl__factory } from '@graphprotocol/interfaces/types' +import { IAccessControl__factory } from '@graphprotocol/issuance/types' +import { expect } from 'chai' + +/** + * Common Interface ID Stability Tests + * + * These tests verify that common interface IDs remain stable across builds. + * These interfaces are used by both allocate and eligibility contracts. + * + * Changes to these IDs indicate breaking changes to the interface definitions. + * + * If a test fails: + * 1. Verify the interface change was intentional + * 2. Understand the impact on deployed contracts + * 3. Update the expected ID if the change is correct + * 4. Document the breaking change in release notes + */ +describe('Common Interface ID Stability', () => { + it('IPausableControl should have stable interface ID', () => { + expect(IPausableControl__factory.interfaceId).to.equal('0xe78a39d8') + }) + + it('IAccessControl should have stable interface ID', () => { + expect(IAccessControl__factory.interfaceId).to.equal('0x7965db0b') + }) +}) diff --git a/packages/issuance/test/tests/common/fixtures.ts b/packages/issuance/test/tests/common/fixtures.ts new file mode 100644 index 000000000..5feaa0e6a --- /dev/null +++ b/packages/issuance/test/tests/common/fixtures.ts @@ -0,0 +1,127 @@ +/** + * Common test fixtures shared by all test domains + * Contains only truly shared functionality used by both allocate and eligibility tests + */ + +import '@nomicfoundation/hardhat-chai-matchers' + +import fs from 'fs' +import hre from 'hardhat' + +const { ethers } = hre +const { upgrades } = require('hardhat') + +import type { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers' + +import { GraphTokenHelper } from './graphTokenHelper' + +/** + * Standard test accounts interface + */ +export interface TestAccounts { + governor: SignerWithAddress + nonGovernor: SignerWithAddress + operator: SignerWithAddress + user: SignerWithAddress + indexer1: SignerWithAddress + indexer2: SignerWithAddress + selfMintingTarget: SignerWithAddress +} + +/** + * Get standard test accounts + */ +export async function getTestAccounts(): Promise { + const [governor, nonGovernor, operator, user, indexer1, indexer2, selfMintingTarget] = await ethers.getSigners() + + return { + governor, + nonGovernor, + operator, + user, + indexer1, + indexer2, + selfMintingTarget, + } +} + +/** + * Common constants used in tests + */ +export const Constants = { + PPM: 1_000_000, // Parts per million (100%) + DEFAULT_ISSUANCE_PER_BLOCK: ethers.parseEther('100'), // 100 GRT per block +} + +// Shared test constants +export const SHARED_CONSTANTS = { + PPM: 1_000_000, + + // Pre-calculated role constants to avoid repeated async calls + GOVERNOR_ROLE: ethers.keccak256(ethers.toUtf8Bytes('GOVERNOR_ROLE')), + OPERATOR_ROLE: ethers.keccak256(ethers.toUtf8Bytes('OPERATOR_ROLE')), + PAUSE_ROLE: ethers.keccak256(ethers.toUtf8Bytes('PAUSE_ROLE')), + ORACLE_ROLE: ethers.keccak256(ethers.toUtf8Bytes('ORACLE_ROLE')), +} as const + +/** + * Deploy a test GraphToken for testing + * This uses the real GraphToken contract + * @returns {Promise} + */ +export async function deployTestGraphToken() { + // Get the governor account + const [governor] = await ethers.getSigners() + + // Load the GraphToken artifact directly from the contracts package + const graphTokenArtifactPath = require.resolve( + '@graphprotocol/contracts/artifacts/contracts/token/GraphToken.sol/GraphToken.json', + ) + const GraphTokenArtifact = JSON.parse(fs.readFileSync(graphTokenArtifactPath, 'utf8')) + + // Create a contract factory using the artifact + const GraphTokenFactory = new ethers.ContractFactory(GraphTokenArtifact.abi, GraphTokenArtifact.bytecode, governor) + + // Deploy the contract + const graphToken = await GraphTokenFactory.deploy(ethers.parseEther('1000000000')) + await graphToken.waitForDeployment() + + return graphToken +} + +/** + * Get a GraphTokenHelper for an existing token + * @param {string} tokenAddress The address of the GraphToken + * @param {boolean} [isFork=false] Whether this is running on a forked network + * @returns {Promise} + */ +export async function getGraphTokenHelper(tokenAddress, isFork = false) { + // Get the governor account + const [governor] = await ethers.getSigners() + + // Get the GraphToken at the specified address + const graphToken = await ethers.getContractAt(isFork ? 'IGraphToken' : 'GraphToken', tokenAddress) + + return new GraphTokenHelper(graphToken, governor) +} + +/** + * Upgrade a contract using OpenZeppelin's upgrades library + * This is a generic function that can be used to upgrade any contract + * @param {string} contractAddress + * @param {string} contractName + * @param {any[]} [constructorArgs=[]] + * @returns {Promise} + */ +export async function upgradeContract(contractAddress, contractName, constructorArgs = []) { + // Get the contract factory + const ContractFactory = await ethers.getContractFactory(contractName) + + // Upgrade the contract + const upgradedContractInstance = await upgrades.upgradeProxy(contractAddress, ContractFactory, { + constructorArgs, + }) + + // Return the upgraded contract instance + return upgradedContractInstance +} diff --git a/packages/issuance/test/tests/common/graphTokenHelper.ts b/packages/issuance/test/tests/common/graphTokenHelper.ts new file mode 100644 index 000000000..f4adbcc8a --- /dev/null +++ b/packages/issuance/test/tests/common/graphTokenHelper.ts @@ -0,0 +1,91 @@ +import fs from 'fs' +import hre from 'hardhat' +const { ethers } = hre +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers' +import { Contract } from 'ethers' + +/** + * Helper class for working with GraphToken in tests + * This provides a consistent interface for minting tokens + * and managing minters + */ +export class GraphTokenHelper { + private graphToken: Contract + private governor: SignerWithAddress + + /** + * Create a new GraphTokenHelper + * @param graphToken The GraphToken instance + * @param governor The governor account + */ + constructor(graphToken: Contract, governor: SignerWithAddress) { + this.graphToken = graphToken + this.governor = governor + } + + /** + * Get the GraphToken instance + */ + getToken(): Contract { + return this.graphToken + } + + /** + * Get the GraphToken address + */ + async getAddress(): Promise { + return await this.graphToken.getAddress() + } + + /** + * Mint tokens to an address + */ + async mint(to: string, amount: bigint): Promise { + await (this.graphToken as any).connect(this.governor).mint(to, amount) + } + + /** + * Add a minter to the GraphToken + */ + async addMinter(minter: string): Promise { + await (this.graphToken as any).connect(this.governor).addMinter(minter) + } + + /** + * Deploy a new GraphToken for testing + * @param {SignerWithAddress} governor The governor account + * @returns {Promise} + */ + static async deploy(governor) { + // Load the GraphToken artifact directly from the contracts package + const graphTokenArtifactPath = require.resolve( + '@graphprotocol/contracts/artifacts/contracts/token/GraphToken.sol/GraphToken.json', + ) + const GraphTokenArtifact = JSON.parse(fs.readFileSync(graphTokenArtifactPath, 'utf8')) + + // Create a contract factory using the artifact + const GraphTokenFactory = new ethers.ContractFactory(GraphTokenArtifact.abi, GraphTokenArtifact.bytecode, governor) + + // Deploy the contract + const graphToken = await GraphTokenFactory.deploy(ethers.parseEther('1000000000')) + await graphToken.waitForDeployment() + + return new GraphTokenHelper(graphToken as any, governor) + } + + /** + * Create a GraphTokenHelper for an existing GraphToken on a forked network + * @param {string} tokenAddress The GraphToken address + * @param {SignerWithAddress} governor The governor account + * @returns {Promise} + */ + static async forFork(tokenAddress, governor) { + // Get the GraphToken at the specified address + const graphToken = await ethers.getContractAt('IGraphToken', tokenAddress) + + // Create a helper + const helper = new GraphTokenHelper(graphToken as any, governor) + + return helper + } +} diff --git a/packages/issuance/test/tests/common/testPatterns.ts b/packages/issuance/test/tests/common/testPatterns.ts new file mode 100644 index 000000000..5af5bc73c --- /dev/null +++ b/packages/issuance/test/tests/common/testPatterns.ts @@ -0,0 +1,52 @@ +/** + * Common test patterns shared by both allocate and eligibility tests + */ + +import { expect } from 'chai' + +/** + * Comprehensive interface compliance test suite + * Replaces multiple individual interface support tests + * + * @param contractGetter - Function that returns the contract instance to test + * @param interfaces - Array of Typechain factory classes with interfaceId and interfaceName + * + * @example + * import { IPausableControl__factory, IAccessControl__factory } from '@graphprotocol/interfaces/types' + * + * shouldSupportInterfaces( + * () => contract, + * [ + * IPausableControl__factory, + * IAccessControl__factory, + * ] + * ) + */ +export function shouldSupportInterfaces( + contractGetter: () => T, + interfaces: Array<{ + interfaceId: string + interfaceName: string + }>, +) { + return function () { + describe('Interface Compliance', () => { + it('should support ERC-165 interface', async function () { + const contract = contractGetter() + expect(await (contract as any).supportsInterface('0x01ffc9a7')).to.be.true + }) + + interfaces.forEach((iface) => { + it(`should support ${iface.interfaceName} interface`, async function () { + const contract = contractGetter() + expect(await (contract as any).supportsInterface(iface.interfaceId)).to.be.true + }) + }) + + it('should not support random interface', async function () { + const contract = contractGetter() + expect(await (contract as any).supportsInterface('0x12345678')).to.be.false + }) + }) + } +} diff --git a/packages/issuance/test/tsconfig.json b/packages/issuance/test/tsconfig.json new file mode 100644 index 000000000..dfecc9bcf --- /dev/null +++ b/packages/issuance/test/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "target": "es2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "skipLibCheck": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "allowJs": true, + "checkJs": false, + "incremental": true, + "noEmitOnError": false, + "noImplicitAny": false, + "outDir": "./artifacts" + }, + "include": ["tests/**/*", "utils/**/*", "../types/**/*"], + "exclude": ["node_modules", "build", "scripts/**/*"] +} diff --git a/packages/issuance/tsconfig.json b/packages/issuance/tsconfig.json new file mode 100644 index 000000000..00aa1b8ef --- /dev/null +++ b/packages/issuance/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2023", + "lib": ["es2023"], + "module": "Node16", + "moduleResolution": "node16", + "strict": true, + "esModuleInterop": true, + "declaration": true, + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "incremental": true + }, + + "include": ["./scripts", "./test", "./typechain"], + "files": ["./hardhat.config.cjs"] +} diff --git a/packages/subgraph-service/contracts/DisputeManager.sol b/packages/subgraph-service/contracts/DisputeManager.sol index 390efb8ce..6f73b2c5d 100644 --- a/packages/subgraph-service/contracts/DisputeManager.sol +++ b/packages/subgraph-service/contracts/DisputeManager.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.27; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-strict-inequalities + +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStaking } from "@graphprotocol/interfaces/contracts/horizon/IHorizonStaking.sol"; import { IDisputeManager } from "@graphprotocol/interfaces/contracts/subgraph-service/IDisputeManager.sol"; import { ISubgraphService } from "@graphprotocol/interfaces/contracts/subgraph-service/ISubgraphService.sol"; @@ -21,7 +24,8 @@ import { AttestationManager } from "./utilities/AttestationManager.sol"; /** * @title DisputeManager - * @notice Provides a way to permissionlessly create disputes for incorrect behavior in the Subgraph Service. + * @author Edge & Node + * @notice Provides a way to permissionlessly create disputes for incorrect behavior in the Subgraph Service * * There are two types of disputes that can be created: Query disputes and Indexing disputes. * diff --git a/packages/subgraph-service/contracts/DisputeManagerStorage.sol b/packages/subgraph-service/contracts/DisputeManagerStorage.sol index 7441efd00..38b6e3115 100644 --- a/packages/subgraph-service/contracts/DisputeManagerStorage.sol +++ b/packages/subgraph-service/contracts/DisputeManagerStorage.sol @@ -7,7 +7,8 @@ import { ISubgraphService } from "@graphprotocol/interfaces/contracts/subgraph-s /** * @title DisputeManagerStorage - * @notice This contract holds all the storage variables for the Dispute Manager contract. + * @author Edge & Node + * @notice This contract holds all the storage variables for the Dispute Manager contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/SubgraphService.sol b/packages/subgraph-service/contracts/SubgraphService.sol index aeb4eb827..0ba0b3035 100644 --- a/packages/subgraph-service/contracts/SubgraphService.sol +++ b/packages/subgraph-service/contracts/SubgraphService.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities +// solhint-disable function-max-lines + import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IGraphTallyCollector } from "@graphprotocol/interfaces/contracts/horizon/IGraphTallyCollector.sol"; import { IRewardsIssuer } from "@graphprotocol/interfaces/contracts/contracts/rewards/IRewardsIssuer.sol"; import { IDataService } from "@graphprotocol/interfaces/contracts/data-service/IDataService.sol"; @@ -26,6 +30,8 @@ import { Allocation } from "./libraries/Allocation.sol"; /** * @title SubgraphService contract + * @author Edge & Node + * @notice A data service contract for subgraph indexing and querying * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/SubgraphServiceStorage.sol b/packages/subgraph-service/contracts/SubgraphServiceStorage.sol index 133963347..04dc4abf9 100644 --- a/packages/subgraph-service/contracts/SubgraphServiceStorage.sol +++ b/packages/subgraph-service/contracts/SubgraphServiceStorage.sol @@ -5,7 +5,8 @@ import { ISubgraphService } from "@graphprotocol/interfaces/contracts/subgraph-s /** * @title SubgraphServiceStorage - * @notice This contract holds all the storage variables for the Subgraph Service contract. + * @author Edge & Node + * @notice This contract holds all the storage variables for the Subgraph Service contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/libraries/Allocation.sol b/packages/subgraph-service/contracts/libraries/Allocation.sol index 88f7195b8..5a4e3cb52 100644 --- a/packages/subgraph-service/contracts/libraries/Allocation.sol +++ b/packages/subgraph-service/contracts/libraries/Allocation.sol @@ -7,7 +7,8 @@ import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; /** * @title Allocation library - * @notice A library to handle Allocations. + * @author Edge & Node + * @notice A library to handle Allocations * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/libraries/Attestation.sol b/packages/subgraph-service/contracts/libraries/Attestation.sol index d821fbfbc..25bb6651f 100644 --- a/packages/subgraph-service/contracts/libraries/Attestation.sol +++ b/packages/subgraph-service/contracts/libraries/Attestation.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities + import { IAttestation } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IAttestation.sol"; /** * @title Attestation library - * @notice A library to handle Attestation. + * @author Edge & Node + * @notice A library to handle Attestation * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ @@ -44,8 +48,8 @@ library Attestation { uint256 private constant BYTES32_BYTE_LENGTH = 32; /** - * @dev Returns if two attestations are conflicting. - * Everything must match except for the responseId. + * @notice Returns if two attestations are conflicting + * @dev Everything must match except for the responseId * @param _attestation1 Attestation * @param _attestation2 Attestation * @return True if the two attestations are conflicting @@ -60,7 +64,7 @@ library Attestation { } /** - * @dev Parse the bytes attestation into a struct from `_data`. + * @notice Parse the bytes attestation into a struct from `_data` * @param _data The bytes to parse * @return Attestation struct */ @@ -87,7 +91,7 @@ library Attestation { } /** - * @dev Parse a uint8 from `_bytes` starting at offset `_start`. + * @notice Parse a uint8 from `_bytes` starting at offset `_start` * @param _bytes The bytes to parse * @param _start The start offset * @return uint8 value @@ -111,7 +115,7 @@ library Attestation { } /** - * @dev Parse a bytes32 from `_bytes` starting at offset `_start`. + * @notice Parse a bytes32 from `_bytes` starting at offset `_start` * @param _bytes The bytes to parse * @param _start The start offset * @return bytes32 value diff --git a/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol b/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol index 2d73abd91..4717cefed 100644 --- a/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol +++ b/packages/subgraph-service/contracts/libraries/LegacyAllocation.sol @@ -6,7 +6,8 @@ import { ILegacyAllocation } from "@graphprotocol/interfaces/contracts/subgraph- /** * @title LegacyAllocation library - * @notice A library to handle legacy Allocations. + * @author Edge & Node + * @notice A library to handle legacy Allocations * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/utilities/AllocationManager.sol b/packages/subgraph-service/contracts/utilities/AllocationManager.sol index e095b3282..08608d8b4 100644 --- a/packages/subgraph-service/contracts/utilities/AllocationManager.sol +++ b/packages/subgraph-service/contracts/utilities/AllocationManager.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events +// solhint-disable gas-small-strings +// solhint-disable function-max-lines + import { IGraphPayments } from "@graphprotocol/interfaces/contracts/horizon/IGraphPayments.sol"; -import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol"; +import { IGraphToken } from "@graphprotocol/interfaces/contracts/contracts/token/IGraphToken.sol"; import { IHorizonStakingTypes } from "@graphprotocol/interfaces/contracts/horizon/internal/IHorizonStakingTypes.sol"; import { IAllocation } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IAllocation.sol"; import { ILegacyAllocation } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/ILegacyAllocation.sol"; @@ -20,7 +25,8 @@ import { ProvisionTracker } from "@graphprotocol/horizon/contracts/data-service/ /** * @title AllocationManager contract - * @notice A helper contract implementing allocation lifecycle management. + * @author Edge & Node + * @notice A helper contract implementing allocation lifecycle management * Allows opening, resizing, and closing allocations, as well as collecting indexing rewards by presenting a Proof * of Indexing (POI). * @custom:security-contact Please email security+contracts@thegraph.com if you find any @@ -63,8 +69,8 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca * @param tokensIndexerRewards The amount of tokens collected for the indexer * @param tokensDelegationRewards The amount of tokens collected for delegators * @param poi The POI presented - * @param currentEpoch The current epoch * @param poiMetadata The metadata associated with the POI + * @param currentEpoch The current epoch */ event IndexingRewardsCollected( address indexed indexer, @@ -95,7 +101,7 @@ abstract contract AllocationManager is EIP712Upgradeable, GraphDirectory, Alloca ); /** - * @dev Emitted when an indexer closes an allocation + * @notice Emitted when an indexer closes an allocation * @param indexer The address of the indexer * @param allocationId The id of the allocation * @param subgraphDeploymentId The id of the subgraph deployment diff --git a/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol b/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol index 4c0870291..a56e649fd 100644 --- a/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol +++ b/packages/subgraph-service/contracts/utilities/AllocationManagerStorage.sol @@ -6,7 +6,8 @@ import { ILegacyAllocation } from "@graphprotocol/interfaces/contracts/subgraph- /** * @title AllocationManagerStorage - * @notice This contract holds all the storage variables for the Allocation Manager contract. + * @author Edge & Node + * @notice This contract holds all the storage variables for the Allocation Manager contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/utilities/AttestationManager.sol b/packages/subgraph-service/contracts/utilities/AttestationManager.sol index f385beeb5..2c45fad3a 100644 --- a/packages/subgraph-service/contracts/utilities/AttestationManager.sol +++ b/packages/subgraph-service/contracts/utilities/AttestationManager.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-small-strings + import { IAttestation } from "@graphprotocol/interfaces/contracts/subgraph-service/internal/IAttestation.sol"; import { AttestationManagerV1Storage } from "./AttestationManagerStorage.sol"; @@ -10,7 +13,8 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I /** * @title AttestationManager contract - * @notice A helper contract implementing attestation verification. + * @author Edge & Node + * @notice A helper contract implementing attestation verification * Uses a custom implementation of EIP712 for backwards compatibility with attestations. * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. @@ -34,18 +38,18 @@ abstract contract AttestationManager is Initializable, AttestationManagerV1Stora bytes32 private constant DOMAIN_SALT = 0xa070ffb1cd7409649bf77822cce74495468e06dbfaef09556838bf188679b9c2; /** - * @dev Initialize the AttestationManager contract and parent contracts + * @notice Initialize the AttestationManager contract and parent contracts */ - // solhint-disable-next-line func-name-mixedcase function __AttestationManager_init() internal onlyInitializing { + // solhint-disable-previous-line func-name-mixedcase __AttestationManager_init_unchained(); } /** - * @dev Initialize the AttestationManager contract + * @notice Initialize the AttestationManager contract */ - // solhint-disable-next-line func-name-mixedcase function __AttestationManager_init_unchained() internal onlyInitializing { + // solhint-disable-previous-line func-name-mixedcase _domainSeparator = keccak256( abi.encode( DOMAIN_TYPE_HASH, @@ -59,7 +63,7 @@ abstract contract AttestationManager is Initializable, AttestationManagerV1Stora } /** - * @dev Recover the signer address of the `_attestation`. + * @notice Recover the signer address of the `_attestation` * @param _attestation The attestation struct * @return Signer address */ diff --git a/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol b/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol index 1c720ec8c..1559a52fa 100644 --- a/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol +++ b/packages/subgraph-service/contracts/utilities/AttestationManagerStorage.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.27; /** * @title AttestationManagerStorage - * @notice This contract holds all the storage variables for the Attestation Manager contract. + * @author Edge & Node + * @notice This contract holds all the storage variables for the Attestation Manager contract * @custom:security-contact Please email security+contracts@thegraph.com if you find any * bugs. We may have an active bug bounty program. */ diff --git a/packages/subgraph-service/contracts/utilities/Directory.sol b/packages/subgraph-service/contracts/utilities/Directory.sol index 6b96616f4..4bfc1daa0 100644 --- a/packages/subgraph-service/contracts/utilities/Directory.sol +++ b/packages/subgraph-service/contracts/utilities/Directory.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.27; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events + import { IDisputeManager } from "@graphprotocol/interfaces/contracts/subgraph-service/IDisputeManager.sol"; import { ISubgraphService } from "@graphprotocol/interfaces/contracts/subgraph-service/ISubgraphService.sol"; import { IGraphTallyCollector } from "@graphprotocol/interfaces/contracts/horizon/IGraphTallyCollector.sol"; @@ -8,7 +11,8 @@ import { ICuration } from "@graphprotocol/interfaces/contracts/contracts/curatio /** * @title Directory contract - * @notice This contract is meant to be inherited by {SubgraphService} contract. + * @author Edge & Node + * @notice This contract is meant to be inherited by {SubgraphService} contract * It contains the addresses of the contracts that the contract interacts with. * Uses immutable variables to minimize gas costs. * @custom:security-contact Please email security+contracts@thegraph.com if you find any diff --git a/packages/subgraph-service/test/unit/mocks/MockRewardsManager.sol b/packages/subgraph-service/test/unit/mocks/MockRewardsManager.sol index b3724daae..8286f2570 100644 --- a/packages/subgraph-service/test/unit/mocks/MockRewardsManager.sol +++ b/packages/subgraph-service/test/unit/mocks/MockRewardsManager.sol @@ -71,6 +71,12 @@ contract MockRewardsManager is IRewardsManager { function calcRewards(uint256, uint256) external pure returns (uint256) {} + function getRewardsIssuancePerBlock() external view returns (uint256) {} + + // -- Setters -- + + function setRewardsEligibilityOracle(address newRewardsEligibilityOracle) external {} + // -- Updates -- function updateAccRewardsPerSignal() external returns (uint256) {} diff --git a/packages/token-distribution/contracts/GraphTokenDistributor.sol b/packages/token-distribution/contracts/GraphTokenDistributor.sol index a7cd88137..79c2dd399 100644 --- a/packages/token-distribution/contracts/GraphTokenDistributor.sol +++ b/packages/token-distribution/contracts/GraphTokenDistributor.sol @@ -2,10 +2,14 @@ pragma solidity ^0.7.3; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-indexed-events, gas-increment-by-one +// solhint-disable named-parameters-mapping + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; /** * @title GraphTokenDistributor diff --git a/packages/token-distribution/contracts/GraphTokenLock.sol b/packages/token-distribution/contracts/GraphTokenLock.sol index 03cfbca43..545b25413 100644 --- a/packages/token-distribution/contracts/GraphTokenLock.sol +++ b/packages/token-distribution/contracts/GraphTokenLock.sol @@ -2,13 +2,16 @@ pragma solidity ^0.7.3; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-indexed-events, gas-strict-inequalities, gas-small-strings + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import { Ownable as OwnableInitializable } from "./Ownable.sol"; -import "./MathUtils.sol"; -import "./IGraphTokenLock.sol"; +import { MathUtils } from "./MathUtils.sol"; +import { IGraphTokenLock } from "./IGraphTokenLock.sol"; /** * @title GraphTokenLock @@ -54,7 +57,7 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { // A cliff set a date to which a beneficiary needs to get to vest // all preceding periods uint256 public vestingCliffTime; - Revocability public revocable; // Whether to use vesting for locked funds + IGraphTokenLock.Revocability public revocable; // Whether to use vesting for locked funds // State @@ -103,7 +106,7 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { uint256 _periods, uint256 _releaseStartTime, uint256 _vestingCliffTime, - Revocability _revocable + IGraphTokenLock.Revocability _revocable ) internal { require(!isInitialized, "Already initialized"); require(_owner != address(0), "Owner cannot be zero"); @@ -113,7 +116,7 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { require(_startTime != 0, "Start time must be set"); require(_startTime < _endTime, "Start time > end time"); require(_periods >= MIN_PERIOD, "Periods cannot be below minimum"); - require(_revocable != Revocability.NotSet, "Must set a revocability option"); + require(_revocable != IGraphTokenLock.Revocability.NotSet, "Must set a revocability option"); require(_releaseStartTime < _endTime, "Release start time must be before end time"); require(_vestingCliffTime < _endTime, "Cliff time must be before end time"); @@ -271,7 +274,7 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { */ function vestedAmount() public view override returns (uint256) { // If non-revocable it is fully vested - if (revocable == Revocability.Disabled) { + if (revocable == IGraphTokenLock.Revocability.Disabled) { return managedAmount; } @@ -298,7 +301,11 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { // Vesting cliff is activated and it has not passed means nothing is vested yet // so funds cannot be released - if (revocable == Revocability.Enabled && vestingCliffTime > 0 && currentTime() < vestingCliffTime) { + if ( + revocable == IGraphTokenLock.Revocability.Enabled && + vestingCliffTime > 0 && + currentTime() < vestingCliffTime + ) { return 0; } @@ -368,7 +375,7 @@ abstract contract GraphTokenLock is OwnableInitializable, IGraphTokenLock { * @dev Vesting schedule is always calculated based on managed tokens */ function revoke() external override onlyOwner { - require(revocable == Revocability.Enabled, "Contract is non-revocable"); + require(revocable == IGraphTokenLock.Revocability.Enabled, "Contract is non-revocable"); require(isRevoked == false, "Already revoked"); uint256 unvestedAmount = managedAmount.sub(vestedAmount()); diff --git a/packages/token-distribution/contracts/GraphTokenLockManager.sol b/packages/token-distribution/contracts/GraphTokenLockManager.sol index 2ec96887e..8eb719685 100644 --- a/packages/token-distribution/contracts/GraphTokenLockManager.sol +++ b/packages/token-distribution/contracts/GraphTokenLockManager.sol @@ -3,14 +3,19 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/EnumerableSet.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-indexed-events, gas-strict-inequalities, gas-increment-by-one +// solhint-disable named-parameters-mapping + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/EnumerableSet.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import "./MinimalProxyFactory.sol"; -import "./IGraphTokenLockManager.sol"; +import { MinimalProxyFactory } from "./MinimalProxyFactory.sol"; +import { IGraphTokenLockManager } from "./IGraphTokenLockManager.sol"; +import { IGraphTokenLock } from "./IGraphTokenLock.sol"; import { GraphTokenLockWallet } from "./GraphTokenLockWallet.sol"; /** diff --git a/packages/token-distribution/contracts/GraphTokenLockSimple.sol b/packages/token-distribution/contracts/GraphTokenLockSimple.sol index e5b00a3fa..9f0ae48a9 100644 --- a/packages/token-distribution/contracts/GraphTokenLockSimple.sol +++ b/packages/token-distribution/contracts/GraphTokenLockSimple.sol @@ -2,7 +2,12 @@ pragma solidity ^0.7.3; -import "./GraphTokenLock.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { GraphTokenLock } from "./GraphTokenLock.sol"; +import { IGraphTokenLock } from "./IGraphTokenLock.sol"; +import { Ownable as OwnableInitializable } from "./Ownable.sol"; /** * @title GraphTokenLockSimple @@ -29,7 +34,7 @@ contract GraphTokenLockSimple is GraphTokenLock { uint256 _periods, uint256 _releaseStartTime, uint256 _vestingCliffTime, - Revocability _revocable + IGraphTokenLock.Revocability _revocable ) external onlyOwner { _initialize( _owner, diff --git a/packages/token-distribution/contracts/GraphTokenLockWallet.sol b/packages/token-distribution/contracts/GraphTokenLockWallet.sol index 3ac24cb13..cbfaab372 100644 --- a/packages/token-distribution/contracts/GraphTokenLockWallet.sol +++ b/packages/token-distribution/contracts/GraphTokenLockWallet.sol @@ -3,12 +3,15 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-increment-by-one, gas-strict-inequalities, gas-small-strings -import "./GraphTokenLock.sol"; -import "./IGraphTokenLockManager.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; + +import { GraphTokenLock, MathUtils } from "./GraphTokenLock.sol"; +import { IGraphTokenLock } from "./IGraphTokenLock.sol"; +import { IGraphTokenLockManager } from "./IGraphTokenLockManager.sol"; /** * @title GraphTokenLockWallet @@ -55,7 +58,7 @@ contract GraphTokenLockWallet is GraphTokenLock { uint256 _periods, uint256 _releaseStartTime, uint256 _vestingCliffTime, - Revocability _revocable + IGraphTokenLock.Revocability _revocable ) external { _initialize( _owner, @@ -130,7 +133,7 @@ contract GraphTokenLockWallet is GraphTokenLock { * @return Amount of tokens ready to be released */ function releasableAmount() public view override returns (uint256) { - if (revocable == Revocability.Disabled) { + if (revocable == IGraphTokenLock.Revocability.Disabled) { return super.releasableAmount(); } @@ -146,7 +149,11 @@ contract GraphTokenLockWallet is GraphTokenLock { // Vesting cliff is activated and it has not passed means nothing is vested yet // so funds cannot be released - if (revocable == Revocability.Enabled && vestingCliffTime > 0 && currentTime() < vestingCliffTime) { + if ( + revocable == IGraphTokenLock.Revocability.Enabled && + vestingCliffTime > 0 && + currentTime() < vestingCliffTime + ) { return 0; } @@ -179,7 +186,7 @@ contract GraphTokenLockWallet is GraphTokenLock { // Tracked used tokens in the protocol // We do this check after balances were updated by the forwarded call // Check is only enforced for revocable contracts to save some gas - if (revocable == Revocability.Enabled) { + if (revocable == IGraphTokenLock.Revocability.Enabled) { // Track contract balance change uint256 newBalance = currentBalance(); if (newBalance < oldBalance) { diff --git a/packages/token-distribution/contracts/ICallhookReceiver.sol b/packages/token-distribution/contracts/ICallhookReceiver.sol deleted file mode 100644 index f8f01d56f..000000000 --- a/packages/token-distribution/contracts/ICallhookReceiver.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -// Copied from graphprotocol/contracts, changed solidity version to 0.7.3 - -/** - * @title Interface for contracts that can receive callhooks through the Arbitrum GRT bridge - * @dev Any contract that can receive a callhook on L2, sent through the bridge from L1, must - * be allowlisted by the governor, but also implement this interface that contains - * the function that will actually be called by the L2GraphTokenGateway. - */ -pragma solidity ^0.7.3; - -interface ICallhookReceiver { - /** - * @notice Receive tokens with a callhook from the bridge - * @param _from Token sender in L1 - * @param _amount Amount of tokens that were transferred - * @param _data ABI-encoded callhook data - */ - function onTokenTransfer(address _from, uint256 _amount, bytes calldata _data) external; -} diff --git a/packages/token-distribution/contracts/IGraphTokenLock.sol b/packages/token-distribution/contracts/IGraphTokenLock.sol index eac89f414..ccdaa004f 100644 --- a/packages/token-distribution/contracts/IGraphTokenLock.sol +++ b/packages/token-distribution/contracts/IGraphTokenLock.sol @@ -3,7 +3,8 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec interface IGraphTokenLock { enum Revocability { diff --git a/packages/token-distribution/contracts/IGraphTokenLockManager.sol b/packages/token-distribution/contracts/IGraphTokenLockManager.sol index c646e5e16..0897f4869 100644 --- a/packages/token-distribution/contracts/IGraphTokenLockManager.sol +++ b/packages/token-distribution/contracts/IGraphTokenLockManager.sol @@ -3,9 +3,12 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec -import "./IGraphTokenLock.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { IGraphTokenLock } from "./IGraphTokenLock.sol"; interface IGraphTokenLockManager { // -- Factory -- diff --git a/packages/token-distribution/contracts/L1GraphTokenLockTransferTool.sol b/packages/token-distribution/contracts/L1GraphTokenLockTransferTool.sol index 962cf54ee..c6d7ffda8 100644 --- a/packages/token-distribution/contracts/L1GraphTokenLockTransferTool.sol +++ b/packages/token-distribution/contracts/L1GraphTokenLockTransferTool.sol @@ -3,8 +3,12 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable function-max-lines, gas-indexed-events, gas-strict-inequalities, use-natspec +// solhint-disable named-parameters-mapping + import { AddressUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; -import { ITokenGateway } from "./arbitrum/ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { L2GraphTokenLockManager } from "./L2GraphTokenLockManager.sol"; import { GraphTokenLockWallet } from "./GraphTokenLockWallet.sol"; @@ -38,12 +42,16 @@ contract L1GraphTokenLockTransferTool is OwnableInitializable, Initializable, Mi using SafeMathUpgradeable for uint256; /// Address of the L1 GRT token contract + // solhint-disable-next-line immutable-vars-naming IERC20 public immutable graphToken; /// Address of the L2GraphTokenLockWallet implementation in L2, used to compute L2 wallet addresses + // solhint-disable-next-line immutable-vars-naming address public immutable l2Implementation; /// Address of the L1GraphTokenGateway contract + // solhint-disable-next-line immutable-vars-naming ITokenGateway public immutable l1Gateway; /// Address of the Staking contract, used to pull ETH for L2 ticket gas + // solhint-disable-next-line immutable-vars-naming address payable public immutable staking; /// L2 lock manager for each L1 lock manager. /// L1 GraphTokenLockManager => L2GraphTokenLockManager diff --git a/packages/token-distribution/contracts/L2GraphTokenLockManager.sol b/packages/token-distribution/contracts/L2GraphTokenLockManager.sol index ee1c30a59..75a2da68c 100644 --- a/packages/token-distribution/contracts/L2GraphTokenLockManager.sol +++ b/packages/token-distribution/contracts/L2GraphTokenLockManager.sol @@ -3,10 +3,14 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-indexed-events, use-natspec +// solhint-disable named-parameters-mapping + import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import { ICallhookReceiver } from "./ICallhookReceiver.sol"; +import { ICallhookReceiver } from "@graphprotocol/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol"; import { GraphTokenLockManager } from "./GraphTokenLockManager.sol"; import { L2GraphTokenLockWallet } from "./L2GraphTokenLockWallet.sol"; @@ -42,8 +46,10 @@ contract L2GraphTokenLockManager is GraphTokenLockManager, ICallhookReceiver { } /// Address of the L2GraphTokenGateway + // solhint-disable-next-line immutable-vars-naming address public immutable l2Gateway; /// Address of the L1 transfer tool contract (in L1, no aliasing) + // solhint-disable-next-line immutable-vars-naming address public immutable l1TransferTool; /// Mapping of each L1 wallet to its L2 wallet counterpart (populated when each wallet is received) /// L1 address => L2 address diff --git a/packages/token-distribution/contracts/L2GraphTokenLockTransferTool.sol b/packages/token-distribution/contracts/L2GraphTokenLockTransferTool.sol index 01010a3a0..0ef769549 100644 --- a/packages/token-distribution/contracts/L2GraphTokenLockTransferTool.sol +++ b/packages/token-distribution/contracts/L2GraphTokenLockTransferTool.sol @@ -3,11 +3,14 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-strict-inequalities, use-natspec + import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { L2GraphTokenLockManager } from "./L2GraphTokenLockManager.sol"; import { L2GraphTokenLockWallet } from "./L2GraphTokenLockWallet.sol"; -import { ITokenGateway } from "./arbitrum/ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; /** * @title L2GraphTokenLockTransferTool contract @@ -16,10 +19,13 @@ import { ITokenGateway } from "./arbitrum/ITokenGateway.sol"; */ contract L2GraphTokenLockTransferTool { /// Address of the L2 GRT token + // solhint-disable-next-line immutable-vars-naming IERC20 public immutable graphToken; /// Address of the L2GraphTokenGateway + // solhint-disable-next-line immutable-vars-naming ITokenGateway public immutable l2Gateway; /// Address of the L1 GRT token (in L1, no aliasing) + // solhint-disable-next-line immutable-vars-naming address public immutable l1GraphToken; /// @dev Emitted when GRT is sent to L1 from a token lock diff --git a/packages/token-distribution/contracts/L2GraphTokenLockWallet.sol b/packages/token-distribution/contracts/L2GraphTokenLockWallet.sol index 905bee460..3fcba490a 100644 --- a/packages/token-distribution/contracts/L2GraphTokenLockWallet.sol +++ b/packages/token-distribution/contracts/L2GraphTokenLockWallet.sol @@ -3,6 +3,9 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { GraphTokenLockWallet } from "./GraphTokenLockWallet.sol"; diff --git a/packages/token-distribution/contracts/MathUtils.sol b/packages/token-distribution/contracts/MathUtils.sol index 742c52c37..22853c809 100644 --- a/packages/token-distribution/contracts/MathUtils.sol +++ b/packages/token-distribution/contracts/MathUtils.sol @@ -2,6 +2,9 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + library MathUtils { function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; diff --git a/packages/token-distribution/contracts/MinimalProxyFactory.sol b/packages/token-distribution/contracts/MinimalProxyFactory.sol index ca1f03ee1..9da66d95e 100644 --- a/packages/token-distribution/contracts/MinimalProxyFactory.sol +++ b/packages/token-distribution/contracts/MinimalProxyFactory.sol @@ -2,6 +2,9 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; diff --git a/packages/token-distribution/contracts/Ownable.sol b/packages/token-distribution/contracts/Ownable.sol index 73ec22821..4e45fcd25 100644 --- a/packages/token-distribution/contracts/Ownable.sol +++ b/packages/token-distribution/contracts/Ownable.sol @@ -2,6 +2,9 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-small-strings + /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to diff --git a/packages/token-distribution/contracts/arbitrum/ITokenGateway.sol b/packages/token-distribution/contracts/arbitrum/ITokenGateway.sol deleted file mode 100644 index bf2968309..000000000 --- a/packages/token-distribution/contracts/arbitrum/ITokenGateway.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright 2020, Offchain Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Originally copied from: - * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals - * - * MODIFIED from Offchain Labs' implementation: - * - Changed solidity version to 0.7.6 (pablo@edgeandnode.com) - * - */ - -pragma solidity ^0.7.3; -pragma experimental ABIEncoderV2; - -interface ITokenGateway { - /// @notice event deprecated in favor of DepositInitiated and WithdrawalInitiated - // event OutboundTransferInitiated( - // address token, - // address indexed _from, - // address indexed _to, - // uint256 indexed _transferId, - // uint256 _amount, - // bytes _data - // ); - - /// @notice event deprecated in favor of DepositFinalized and WithdrawalFinalized - // event InboundTransferFinalized( - // address token, - // address indexed _from, - // address indexed _to, - // uint256 indexed _transferId, - // uint256 _amount, - // bytes _data - // ); - - function outboundTransfer( - address _token, - address _to, - uint256 _amount, - uint256 _maxGas, - uint256 _gasPriceBid, - bytes calldata _data - ) external payable returns (bytes memory); - - function finalizeInboundTransfer( - address _token, - address _from, - address _to, - uint256 _amount, - bytes calldata _data - ) external payable; - - /** - * @notice Calculate the address used when bridging an ERC20 token - * @dev the L1 and L2 address oracles may not always be in sync. - * For example, a custom token may have been registered but not deployed or the contract self destructed. - * @param l1ERC20 address of L1 token - * @return L2 address of a bridged ERC20 token - */ - function calculateL2TokenAddress(address l1ERC20) external view returns (address); -} diff --git a/packages/token-distribution/contracts/tests/BridgeMock.sol b/packages/token-distribution/contracts/tests/BridgeMock.sol index 643a20428..48bfbbec3 100644 --- a/packages/token-distribution/contracts/tests/BridgeMock.sol +++ b/packages/token-distribution/contracts/tests/BridgeMock.sol @@ -2,7 +2,10 @@ pragma solidity ^0.7.3; -import "./arbitrum/IBridge.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable gas-increment-by-one, use-natspec + +import { IBridge } from "./arbitrum/IBridge.sol"; /** * @title Arbitrum Bridge mock contract diff --git a/packages/token-distribution/contracts/tests/GraphTokenMock.sol b/packages/token-distribution/contracts/tests/GraphTokenMock.sol index bc52b9456..f513c92d6 100644 --- a/packages/token-distribution/contracts/tests/GraphTokenMock.sol +++ b/packages/token-distribution/contracts/tests/GraphTokenMock.sol @@ -2,8 +2,11 @@ pragma solidity ^0.7.3; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; /** * @title Graph Token Mock contract. diff --git a/packages/token-distribution/contracts/tests/InboxMock.sol b/packages/token-distribution/contracts/tests/InboxMock.sol index 9c16ee4ab..fa6b28c82 100644 --- a/packages/token-distribution/contracts/tests/InboxMock.sol +++ b/packages/token-distribution/contracts/tests/InboxMock.sol @@ -2,8 +2,12 @@ pragma solidity ^0.7.3; -import "./arbitrum/IInbox.sol"; -import "./arbitrum/AddressAliasHelper.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { IInbox } from "./arbitrum/IInbox.sol"; +import { AddressAliasHelper } from "./arbitrum/AddressAliasHelper.sol"; +import { IBridge } from "./arbitrum/IBridge.sol"; /** * @title Arbitrum Inbox mock contract diff --git a/packages/token-distribution/contracts/tests/L1TokenGatewayMock.sol b/packages/token-distribution/contracts/tests/L1TokenGatewayMock.sol index 679bcb0ad..fdbab7bcb 100644 --- a/packages/token-distribution/contracts/tests/L1TokenGatewayMock.sol +++ b/packages/token-distribution/contracts/tests/L1TokenGatewayMock.sol @@ -2,10 +2,12 @@ pragma solidity ^0.7.3; +// solhint-disable gas-increment-by-one, gas-indexed-events, gas-strict-inequalities, use-natspec + import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { ITokenGateway } from "../arbitrum//ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; /** * @title L1 Token Gateway mock contract diff --git a/packages/token-distribution/contracts/tests/L2TokenGatewayMock.sol b/packages/token-distribution/contracts/tests/L2TokenGatewayMock.sol index c9e12dd74..02a5383e4 100644 --- a/packages/token-distribution/contracts/tests/L2TokenGatewayMock.sol +++ b/packages/token-distribution/contracts/tests/L2TokenGatewayMock.sol @@ -2,10 +2,12 @@ pragma solidity ^0.7.3; +// solhint-disable gas-increment-by-one, gas-indexed-events, use-natspec + import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { ITokenGateway } from "../arbitrum//ITokenGateway.sol"; +import { ITokenGateway } from "@graphprotocol/interfaces/contracts/contracts/arbitrum/ITokenGateway.sol"; import { GraphTokenMock } from "./GraphTokenMock.sol"; -import { ICallhookReceiver } from "../ICallhookReceiver.sol"; +import { ICallhookReceiver } from "@graphprotocol/interfaces/contracts/contracts/gateway/ICallhookReceiver.sol"; /** * @title L2 Token Gateway mock contract @@ -13,8 +15,10 @@ import { ICallhookReceiver } from "../ICallhookReceiver.sol"; */ contract L2TokenGatewayMock is Ownable { /// Address of the L1 GRT contract + // solhint-disable-next-line immutable-vars-naming address public immutable l1Token; /// Address of the L2 GRT contract + // solhint-disable-next-line immutable-vars-naming address public immutable l2Token; /// Next ID to return when sending an outboundTransfer uint256 public nextId; diff --git a/packages/token-distribution/contracts/tests/Stakes.sol b/packages/token-distribution/contracts/tests/Stakes.sol index bf140aa8f..81d5480fa 100644 --- a/packages/token-distribution/contracts/tests/Stakes.sol +++ b/packages/token-distribution/contracts/tests/Stakes.sol @@ -3,7 +3,10 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/math/SafeMath.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; /** * @title A collection of data structures and functions to manage the Indexer Stake state. diff --git a/packages/token-distribution/contracts/tests/StakingMock.sol b/packages/token-distribution/contracts/tests/StakingMock.sol index 8c5fffc80..b4c1871ed 100644 --- a/packages/token-distribution/contracts/tests/StakingMock.sol +++ b/packages/token-distribution/contracts/tests/StakingMock.sol @@ -3,48 +3,71 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// solhint-disable named-parameters-mapping +// solhint-disable gas-strict-inequalities -import "./Stakes.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { Stakes } from "./Stakes.sol"; + +/** + * @title StakingMock contract + * @author Edge & Node + * @notice A mock contract for testing staking functionality + */ contract StakingMock { using SafeMath for uint256; using Stakes for Stakes.Indexer; // -- State -- + /// @notice Minimum stake required for indexers uint256 public minimumIndexerStake = 100e18; + /// @notice Thawing period in blocks uint256 public thawingPeriod = 10; // 10 blocks + /// @notice The token contract IERC20 public token; - // Indexer stakes : indexer => Stake + /// @notice Indexer stakes mapping mapping(address => Stakes.Indexer) public stakes; /** - * @dev Emitted when `indexer` stake `tokens` amount. + * @notice Emitted when indexer stakes tokens + * @param indexer The indexer address + * @param tokens The amount of tokens staked */ - event StakeDeposited(address indexed indexer, uint256 tokens); + event StakeDeposited(address indexed indexer, uint256 indexed tokens); /** - * @dev Emitted when `indexer` unstaked and locked `tokens` amount `until` block. + * @notice Emitted when indexer unstakes and locks tokens + * @param indexer The indexer address + * @param tokens The amount of tokens locked + * @param until The block number until which tokens are locked */ - event StakeLocked(address indexed indexer, uint256 tokens, uint256 until); + event StakeLocked(address indexed indexer, uint256 indexed tokens, uint256 indexed until); /** - * @dev Emitted when `indexer` withdrew `tokens` staked. + * @notice Emitted when indexer withdraws staked tokens + * @param indexer The indexer address + * @param tokens The amount of tokens withdrawn */ - event StakeWithdrawn(address indexed indexer, uint256 tokens); + event StakeWithdrawn(address indexed indexer, uint256 indexed tokens); - // Contract constructor. + /** + * @notice Contract constructor + * @param _token The token contract address + */ constructor(IERC20 _token) { require(address(_token) != address(0), "!token"); token = _token; } + /// @notice Receive function to accept ETH receive() external payable {} /** - * @dev Deposit tokens on the indexer stake. + * @notice Deposit tokens on the indexer stake * @param _tokens Amount of tokens to stake */ function stake(uint256 _tokens) external { @@ -52,7 +75,7 @@ contract StakingMock { } /** - * @dev Deposit tokens on the indexer stake. + * @notice Deposit tokens on the indexer stake * @param _indexer Address of the indexer * @param _tokens Amount of tokens to stake */ @@ -70,7 +93,7 @@ contract StakingMock { } /** - * @dev Unstake tokens from the indexer stake, lock them until thawing period expires. + * @notice Unstake tokens from the indexer stake, lock them until thawing period expires * @param _tokens Amount of tokens to unstake */ function unstake(uint256 _tokens) external { @@ -97,12 +120,17 @@ contract StakingMock { } /** - * @dev Withdraw indexer tokens once the thawing period has passed. + * @notice Withdraw indexer tokens once the thawing period has passed */ function withdraw() external { _withdraw(msg.sender); } + /** + * @notice Internal function to stake tokens for an indexer + * @param _indexer Address of the indexer + * @param _tokens Amount of tokens to stake + */ function _stake(address _indexer, uint256 _tokens) internal { // Deposit tokens into the indexer stake Stakes.Indexer storage indexerStake = stakes[_indexer]; @@ -112,7 +140,7 @@ contract StakingMock { } /** - * @dev Withdraw indexer tokens once the thawing period has passed. + * @notice Withdraw indexer tokens once the thawing period has passed * @param _indexer Address of indexer to withdraw funds from */ function _withdraw(address _indexer) private { diff --git a/packages/token-distribution/contracts/tests/WalletMock.sol b/packages/token-distribution/contracts/tests/WalletMock.sol index 872760d6e..b526e547b 100644 --- a/packages/token-distribution/contracts/tests/WalletMock.sol +++ b/packages/token-distribution/contracts/tests/WalletMock.sol @@ -3,6 +3,9 @@ pragma solidity ^0.7.3; pragma experimental ABIEncoderV2; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + import { Address } from "@openzeppelin/contracts/utils/Address.sol"; /** @@ -14,14 +17,19 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; */ contract WalletMock { /// Target contract for the fallback function (usually a transfer tool contract) + // solhint-disable-next-line immutable-vars-naming address public immutable target; /// Address of the GRT (mock) token + // solhint-disable-next-line immutable-vars-naming address public immutable token; /// Address of the wallet's manager + // solhint-disable-next-line immutable-vars-naming address public immutable manager; /// Whether the wallet has been initialized + // solhint-disable-next-line immutable-vars-naming bool public immutable isInitialized; /// Whether the beneficiary has accepted the lock + // solhint-disable-next-line immutable-vars-naming bool public immutable isAccepted; /** diff --git a/packages/token-distribution/contracts/tests/arbitrum/AddressAliasHelper.sol b/packages/token-distribution/contracts/tests/arbitrum/AddressAliasHelper.sol index 146c1c876..525e71640 100644 --- a/packages/token-distribution/contracts/tests/arbitrum/AddressAliasHelper.sol +++ b/packages/token-distribution/contracts/tests/arbitrum/AddressAliasHelper.sol @@ -25,8 +25,12 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + library AddressAliasHelper { - uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + // solhint-disable-next-line const-name-snakecase + uint160 internal constant offset = uint160(0x1111000000000000000000000000000000001111); /// @notice Utility function that converts the address in the L1 that submitted a tx to /// the inbox to the msg.sender viewed in the L2 diff --git a/packages/token-distribution/contracts/tests/arbitrum/IBridge.sol b/packages/token-distribution/contracts/tests/arbitrum/IBridge.sol index fdfa34eed..f4ace5767 100644 --- a/packages/token-distribution/contracts/tests/arbitrum/IBridge.sol +++ b/packages/token-distribution/contracts/tests/arbitrum/IBridge.sol @@ -25,6 +25,9 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec, gas-indexed-events + interface IBridge { event MessageDelivered( uint256 indexed messageIndex, diff --git a/packages/token-distribution/contracts/tests/arbitrum/IInbox.sol b/packages/token-distribution/contracts/tests/arbitrum/IInbox.sol index 0a6e78dc3..a3b7b096a 100644 --- a/packages/token-distribution/contracts/tests/arbitrum/IInbox.sol +++ b/packages/token-distribution/contracts/tests/arbitrum/IInbox.sol @@ -25,8 +25,11 @@ pragma solidity ^0.7.3; -import "./IBridge.sol"; -import "./IMessageProvider.sol"; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + +import { IBridge } from "./IBridge.sol"; +import { IMessageProvider } from "./IMessageProvider.sol"; interface IInbox is IMessageProvider { function sendL2Message(bytes calldata messageData) external returns (uint256); diff --git a/packages/token-distribution/contracts/tests/arbitrum/IMessageProvider.sol b/packages/token-distribution/contracts/tests/arbitrum/IMessageProvider.sol index cf8446af2..28b3937e8 100644 --- a/packages/token-distribution/contracts/tests/arbitrum/IMessageProvider.sol +++ b/packages/token-distribution/contracts/tests/arbitrum/IMessageProvider.sol @@ -25,6 +25,9 @@ pragma solidity ^0.7.3; +// TODO: Re-enable and fix issues when publishing a new version +// solhint-disable use-natspec + interface IMessageProvider { event InboxMessageDelivered(uint256 indexed messageNum, bytes data); diff --git a/packages/token-distribution/package.json b/packages/token-distribution/package.json index f02699c4b..21019d6e6 100644 --- a/packages/token-distribution/package.json +++ b/packages/token-distribution/package.json @@ -46,6 +46,7 @@ "@ethersproject/providers": "^5.7.0", "@graphprotocol/client-cli": "^2.2.22", "@graphprotocol/contracts": "workspace:^", + "@graphprotocol/interfaces": "workspace:^", "@graphql-yoga/plugin-persisted-operations": "^3.13.5", "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-etherscan": "^3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1661a294a..38ba5cfde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,12 +21,21 @@ catalogs: '@nomicfoundation/hardhat-ethers': specifier: ^3.1.0 version: 3.1.0 + '@nomicfoundation/hardhat-verify': + specifier: ^2.0.10 + version: 2.1.1 + '@typechain/hardhat': + specifier: ^9.0.0 + version: 9.1.0 '@typescript-eslint/eslint-plugin': specifier: ^8.46.1 version: 8.46.1 '@typescript-eslint/parser': specifier: ^8.46.1 version: 8.46.1 + dotenv: + specifier: ^16.5.0 + version: 16.6.1 eslint: specifier: ^9.37.0 version: 9.37.0 @@ -66,6 +75,9 @@ catalogs: hardhat-ignore-warnings: specifier: ^0.2.12 version: 0.2.12 + hardhat-secure-accounts: + specifier: ^1.0.5 + version: 1.0.5 hardhat-storage-layout: specifier: ^0.1.7 version: 0.1.7 @@ -74,7 +86,7 @@ catalogs: version: 9.1.7 lint-staged: specifier: ^16.2.4 - version: 16.2.3 + version: 16.2.4 markdownlint-cli: specifier: ^0.45.0 version: 0.45.0 @@ -95,7 +107,7 @@ catalogs: version: 5.9.3 typescript-eslint: specifier: ^8.46.1 - version: 8.45.0 + version: 8.46.1 yaml-lint: specifier: ^1.7.0 version: 1.7.0 @@ -156,7 +168,7 @@ importers: version: 9.1.7 lint-staged: specifier: 'catalog:' - version: 16.2.3 + version: 16.2.4 markdownlint-cli: specifier: 'catalog:' version: 0.45.0 @@ -174,7 +186,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: 'catalog:' - version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) yaml-lint: specifier: 'catalog:' version: 1.7.0 @@ -873,7 +885,7 @@ importers: version: 1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) lint-staged: specifier: 'catalog:' - version: 16.2.3 + version: 16.2.4 prettier: specifier: 'catalog:' version: 3.6.2 @@ -953,6 +965,185 @@ importers: specifier: ^2.31.7 version: 2.37.13(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.1.11) + packages/issuance: + dependencies: + '@noble/hashes': + specifier: ^1.8.0 + version: 1.8.0 + devDependencies: + '@graphprotocol/interfaces': + specifier: workspace:^ + version: link:../interfaces + '@graphprotocol/toolshed': + specifier: workspace:^ + version: link:../toolshed + '@nomicfoundation/hardhat-ethers': + specifier: 'catalog:' + version: 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': + specifier: 'catalog:' + version: 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@openzeppelin/contracts': + specifier: ^5.4.0 + version: 5.4.0 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.4.0 + version: 5.4.0(@openzeppelin/contracts@5.4.0) + '@openzeppelin/hardhat-upgrades': + specifier: ^3.9.0 + version: 3.9.1(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(encoding@0.1.13)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@typechain/ethers-v6': + specifier: ^0.5.0 + version: 0.5.1(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3) + '@typechain/hardhat': + specifier: 'catalog:' + version: 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3)) + '@types/node': + specifier: ^20.17.50 + version: 20.19.21 + dotenv: + specifier: 'catalog:' + version: 16.6.1 + eslint: + specifier: 'catalog:' + version: 9.37.0(jiti@2.6.1) + ethers: + specifier: 'catalog:' + version: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + glob: + specifier: 'catalog:' + version: 11.0.3 + globals: + specifier: 'catalog:' + version: 16.4.0 + hardhat: + specifier: 'catalog:' + version: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat-contract-sizer: + specifier: 'catalog:' + version: 2.10.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + hardhat-secure-accounts: + specifier: 'catalog:' + version: 1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + hardhat-storage-layout: + specifier: 'catalog:' + version: 0.1.7(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + lint-staged: + specifier: 'catalog:' + version: 16.2.4 + markdownlint-cli: + specifier: 'catalog:' + version: 0.45.0 + prettier: + specifier: 'catalog:' + version: 3.6.2 + prettier-plugin-solidity: + specifier: 'catalog:' + version: 2.1.0(prettier@3.6.2) + solhint: + specifier: 'catalog:' + version: 6.0.1(typescript@5.9.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.21)(typescript@5.9.3) + typechain: + specifier: ^8.3.0 + version: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + typescript-eslint: + specifier: 'catalog:' + version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + yaml-lint: + specifier: 'catalog:' + version: 1.7.0 + + packages/issuance/test: + dependencies: + '@graphprotocol/contracts': + specifier: workspace:^ + version: link:../../contracts + '@graphprotocol/interfaces': + specifier: workspace:^ + version: link:../../interfaces + '@graphprotocol/issuance': + specifier: workspace:^ + version: link:.. + devDependencies: + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^2.0.0 + version: 2.1.0(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': + specifier: 'catalog:' + version: 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-foundry': + specifier: ^1.1.1 + version: 1.2.0(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-network-helpers': + specifier: ^1.0.0 + version: 1.1.0(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-toolbox': + specifier: 5.0.0 + version: 5.0.0(b4f70cf64ecce74e72d0564059c80166) + '@openzeppelin/contracts': + specifier: ^5.4.0 + version: 5.4.0 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.4.0 + version: 5.4.0(@openzeppelin/contracts@5.4.0) + '@openzeppelin/foundry-upgrades': + specifier: 0.4.0 + version: 0.4.0(@openzeppelin/defender-deploy-client-cli@0.0.1-alpha.10(encoding@0.1.13))(@openzeppelin/upgrades-core@1.44.1) + '@types/chai': + specifier: ^4.3.20 + version: 4.3.20 + '@types/mocha': + specifier: ^10.0.10 + version: 10.0.10 + '@types/node': + specifier: ^20.17.50 + version: 20.19.21 + chai: + specifier: ^4.3.7 + version: 4.5.0 + dotenv: + specifier: ^16.5.0 + version: 16.6.1 + eslint: + specifier: 'catalog:' + version: 9.37.0(jiti@2.6.1) + eslint-plugin-no-only-tests: + specifier: 'catalog:' + version: 3.3.0 + ethers: + specifier: 'catalog:' + version: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + forge-std: + specifier: https://github.com/foundry-rs/forge-std/tarball/v1.9.7 + version: https://github.com/foundry-rs/forge-std/tarball/v1.9.7 + glob: + specifier: 'catalog:' + version: 11.0.3 + hardhat: + specifier: 'catalog:' + version: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat-gas-reporter: + specifier: 'catalog:' + version: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + prettier: + specifier: 'catalog:' + version: 3.6.2 + solidity-coverage: + specifier: ^0.8.0 + version: 0.8.16(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.21)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + packages/subgraph-service: devDependencies: '@graphprotocol/contracts': @@ -1053,7 +1244,7 @@ importers: version: 2.2.3 lint-staged: specifier: 'catalog:' - version: 16.2.3 + version: 16.2.4 prettier: specifier: 'catalog:' version: 3.6.2 @@ -1106,6 +1297,9 @@ importers: '@graphprotocol/contracts': specifier: workspace:^ version: link:../contracts + '@graphprotocol/interfaces': + specifier: workspace:^ + version: link:../interfaces '@graphql-yoga/plugin-persisted-operations': specifier: ^3.13.5 version: 3.16.0(graphql-yoga@5.16.0(graphql@16.11.0))(graphql@16.11.0) @@ -1222,7 +1416,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: 'catalog:' - version: 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) packages/toolshed: dependencies: @@ -3356,6 +3550,28 @@ packages: typechain: ^8.3.0 typescript: '>=4.5.0' + '@nomicfoundation/hardhat-toolbox@5.0.0': + resolution: {integrity: sha512-FnUtUC5PsakCbwiVNsqlXVIWG5JIb5CEZoSXbJUsEBun22Bivx2jhF1/q9iQbzuaGpJKFQyOhemPB2+XlEE6pQ==} + peerDependencies: + '@nomicfoundation/hardhat-chai-matchers': ^2.0.0 + '@nomicfoundation/hardhat-ethers': ^3.0.0 + '@nomicfoundation/hardhat-ignition-ethers': ^0.15.0 + '@nomicfoundation/hardhat-network-helpers': ^1.0.0 + '@nomicfoundation/hardhat-verify': ^2.0.0 + '@typechain/ethers-v6': ^0.5.0 + '@typechain/hardhat': ^9.0.0 + '@types/chai': ^4.2.0 + '@types/mocha': '>=9.1.0' + '@types/node': ^20.17.50 + chai: ^4.2.0 + ethers: ^6.4.0 + hardhat: ^2.11.0 + hardhat-gas-reporter: ^1.0.8 + solidity-coverage: ^0.8.1 + ts-node: '>=8.0.0' + typechain: ^8.3.0 + typescript: '>=4.5.0' + '@nomicfoundation/hardhat-verify@2.1.1': resolution: {integrity: sha512-K1plXIS42xSHDJZRkrE2TZikqxp9T4y6jUMUNI/imLgN5uCcEQokmfU0DlyP9zzHncYK92HlT5IWP35UVCLrPw==} peerDependencies: @@ -3491,6 +3707,18 @@ packages: '@nomiclabs/harhdat-etherscan': optional: true + '@openzeppelin/hardhat-upgrades@3.9.1': + resolution: {integrity: sha512-pSDjlOnIpP+PqaJVe144dK6VVKZw2v6YQusyt0OOLiCsl+WUzfo4D0kylax7zjrOxqy41EK2ipQeIF4T+cCn2A==} + hasBin: true + peerDependencies: + '@nomicfoundation/hardhat-ethers': ^3.0.6 + '@nomicfoundation/hardhat-verify': ^2.0.14 + ethers: ^6.6.0 + hardhat: ^2.24.1 + peerDependenciesMeta: + '@nomicfoundation/hardhat-verify': + optional: true + '@openzeppelin/platform-deploy-client@0.8.0': resolution: {integrity: sha512-POx3AsnKwKSV/ZLOU/gheksj0Lq7Is1q2F3pKmcFjGZiibf+4kjGxr4eSMrT+2qgKYZQH1ZLQZ+SkbguD8fTvA==} deprecated: '@openzeppelin/platform-deploy-client is deprecated. Please use @openzeppelin/defender-sdk-deploy-client' @@ -4140,14 +4368,6 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.45.0': - resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.45.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/eslint-plugin@8.46.1': resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4156,13 +4376,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.45.0': - resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.1': resolution: {integrity: sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4170,45 +4383,22 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.45.0': - resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.46.1': resolution: {integrity: sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.45.0': - resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.46.1': resolution: {integrity: sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.45.0': - resolution: {integrity: sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.46.1': resolution: {integrity: sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.45.0': - resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.1': resolution: {integrity: sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4216,33 +4406,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.45.0': - resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.46.1': resolution: {integrity: sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.45.0': - resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/typescript-estree@8.46.1': resolution: {integrity: sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.45.0': - resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.1': resolution: {integrity: sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4250,10 +4423,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.45.0': - resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.46.1': resolution: {integrity: sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8278,8 +8447,8 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - lint-staged@16.2.3: - resolution: {integrity: sha512-1OnJEESB9zZqsp61XHH2fvpS1es3hRCxMplF/AJUDa8Ho8VrscYDIuxGrj3m8KPXbcWZ8fT9XTMUhEQmOVKpKw==} + lint-staged@16.2.4: + resolution: {integrity: sha512-Pkyr/wd90oAyXk98i/2KwfkIhoYQUMtss769FIT9hFM5ogYZwrk+GRE46yKXSg2ZGhcJ1p38Gf5gmI5Ohjg2yg==} engines: {node: '>=20.17'} hasBin: true @@ -8964,8 +9133,8 @@ packages: nano-json-stream-parser@0.1.2: resolution: {integrity: sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==} - nano-spawn@1.0.3: - resolution: {integrity: sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA==} + nano-spawn@2.0.0: + resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} engines: {node: '>=20.17'} nanomatch@1.2.13: @@ -11305,8 +11474,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.45.0: - resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==} + typescript-eslint@8.46.1: + resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -11376,6 +11545,10 @@ packages: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} + undici@6.22.0: + resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} + engines: {node: '>=18.17'} + unfetch@4.2.0: resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} @@ -15715,6 +15888,11 @@ snapshots: hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) picocolors: 1.1.1 + '@nomicfoundation/hardhat-foundry@1.2.0(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + dependencies: + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + picocolors: 1.1.1 + '@nomicfoundation/hardhat-ignition-ethers@0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -15723,6 +15901,14 @@ snapshots: ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@nomicfoundation/hardhat-ignition-ethers@0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + dependencies: + '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ignition': 0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@nomicfoundation/ignition-core': 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: '@nomicfoundation/hardhat-verify': 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -15739,6 +15925,22 @@ snapshots: - supports-color - utf-8-validate + '@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': + dependencies: + '@nomicfoundation/hardhat-verify': 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/ignition-core': 0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@nomicfoundation/ignition-ui': 0.15.12 + chalk: 4.1.2 + debug: 4.4.3(supports-color@9.4.0) + fs-extra: 10.1.0 + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + json5: 2.2.3 + prompts: 2.4.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@nomicfoundation/hardhat-network-helpers@1.1.0(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: ethereumjs-util: 7.1.5 @@ -15789,6 +15991,27 @@ snapshots: typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 + '@nomicfoundation/hardhat-toolbox@5.0.0(b4f70cf64ecce74e72d0564059c80166)': + dependencies: + '@nomicfoundation/hardhat-chai-matchers': 2.1.0(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ignition-ethers': 0.15.14(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.13(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.13(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-network-helpers': 1.1.0(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-verify': 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@typechain/ethers-v6': 0.5.1(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3)) + '@types/chai': 4.3.20 + '@types/mocha': 10.0.10 + '@types/node': 20.19.21 + chai: 4.5.0 + ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat-gas-reporter: 1.0.10(bufferutil@4.0.9)(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + solidity-coverage: 0.8.16(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + ts-node: 10.9.2(@types/node@20.19.21)(typescript@5.9.3) + typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) + typescript: 5.9.3 + '@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: '@ethersproject/abi': 5.8.0 @@ -15970,8 +16193,8 @@ snapshots: '@openzeppelin/defender-deploy-client-cli@0.0.1-alpha.10(encoding@0.1.13)': dependencies: '@openzeppelin/defender-sdk-base-client': 2.7.0(encoding@0.1.13) - '@openzeppelin/defender-sdk-deploy-client': 2.7.0(encoding@0.1.13) - '@openzeppelin/defender-sdk-network-client': 2.7.0(encoding@0.1.13) + '@openzeppelin/defender-sdk-deploy-client': 2.7.0(debug@4.4.3)(encoding@0.1.13) + '@openzeppelin/defender-sdk-network-client': 2.7.0(debug@4.4.3)(encoding@0.1.13) dotenv: 16.6.1 minimist: 1.2.8 transitivePeerDependencies: @@ -15988,7 +16211,7 @@ snapshots: - aws-crt - encoding - '@openzeppelin/defender-sdk-deploy-client@2.7.0(encoding@0.1.13)': + '@openzeppelin/defender-sdk-deploy-client@2.7.0(debug@4.4.3)(encoding@0.1.13)': dependencies: '@openzeppelin/defender-sdk-base-client': 2.7.0(encoding@0.1.13) axios: 1.12.2(debug@4.4.3) @@ -15998,7 +16221,7 @@ snapshots: - debug - encoding - '@openzeppelin/defender-sdk-network-client@2.7.0(encoding@0.1.13)': + '@openzeppelin/defender-sdk-network-client@2.7.0(debug@4.4.3)(encoding@0.1.13)': dependencies: '@openzeppelin/defender-sdk-base-client': 2.7.0(encoding@0.1.13) axios: 1.12.2(debug@4.4.3) @@ -16029,6 +16252,27 @@ snapshots: - encoding - supports-color + '@openzeppelin/hardhat-upgrades@3.9.1(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-verify@2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(encoding@0.1.13)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': + dependencies: + '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + '@openzeppelin/defender-sdk-base-client': 2.7.0(encoding@0.1.13) + '@openzeppelin/defender-sdk-deploy-client': 2.7.0(debug@4.4.3)(encoding@0.1.13) + '@openzeppelin/defender-sdk-network-client': 2.7.0(debug@4.4.3)(encoding@0.1.13) + '@openzeppelin/upgrades-core': 1.44.1 + chalk: 4.1.2 + debug: 4.4.3(supports-color@9.4.0) + ethereumjs-util: 7.1.5 + ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + proper-lockfile: 4.1.2 + undici: 6.22.0 + optionalDependencies: + '@nomicfoundation/hardhat-verify': 2.1.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + transitivePeerDependencies: + - aws-crt + - encoding + - supports-color + '@openzeppelin/platform-deploy-client@0.8.0(debug@4.4.3)(encoding@0.1.13)': dependencies: '@ethersproject/abi': 5.8.0 @@ -16813,17 +17057,17 @@ snapshots: '@types/bn.js@4.11.6': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/bn.js@5.2.0': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/responselike': 1.0.3 optional: true @@ -16835,7 +17079,7 @@ snapshots: '@types/concat-stream@1.6.1': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/conventional-commits-parser@5.0.1': dependencies: @@ -16849,12 +17093,12 @@ snapshots: '@types/form-data@0.0.33': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/glob@8.1.0': dependencies: @@ -16889,7 +17133,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 optional: true '@types/level-errors@3.0.2': {} @@ -16898,7 +17142,7 @@ snapshots: dependencies: '@types/abstract-leveldown': 7.2.5 '@types/level-errors': 3.0.2 - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/minimatch@5.1.2': {} @@ -16908,7 +17152,7 @@ snapshots: '@types/mkdirp@0.5.2': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/mocha@10.0.10': {} @@ -16918,7 +17162,7 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 form-data: 4.0.4 '@types/node@20.19.19': @@ -16931,7 +17175,7 @@ snapshots: '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/prettier@2.7.3': {} @@ -16939,16 +17183,16 @@ snapshots: '@types/resolve@0.0.8': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/responselike@1.0.3': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 optional: true '@types/secp256k1@4.0.7': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/sinon-chai@3.2.12': dependencies: @@ -16971,7 +17215,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/yargs-parser@21.0.3': {} @@ -16979,23 +17223,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 - eslint: 9.37.0(jiti@2.6.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -17013,18 +17240,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 - debug: 4.4.3(supports-color@9.4.0) - eslint: 9.37.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.46.1 @@ -17037,15 +17252,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.45.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) - '@typescript-eslint/types': 8.46.1 - debug: 4.4.3(supports-color@9.4.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.46.1(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) @@ -17055,36 +17261,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.45.0': - dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 - '@typescript-eslint/scope-manager@8.46.1': dependencies: '@typescript-eslint/types': 8.46.1 '@typescript-eslint/visitor-keys': 8.46.1 - '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.46.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3(supports-color@9.4.0) - eslint: 9.37.0(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.46.1 @@ -17097,26 +17282,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.45.0': {} - '@typescript-eslint/types@8.46.1': {} - '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.45.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 - debug: 4.4.3(supports-color@9.4.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.3 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.46.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.46.1(typescript@5.9.3) @@ -17133,17 +17300,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - eslint: 9.37.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) @@ -17155,11 +17311,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.45.0': - dependencies: - '@typescript-eslint/types': 8.45.0 - eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.46.1': dependencies: '@typescript-eslint/types': 8.46.1 @@ -18258,7 +18409,7 @@ snapshots: bip39@3.0.4: dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 create-hash: 1.2.0 pbkdf2: 3.1.5 randombytes: 2.1.0 @@ -19352,7 +19503,7 @@ snapshots: dependencies: is-arguments: 1.2.0 is-date-object: 1.1.0 - is-regex: 1.1.4 + is-regex: 1.2.1 object-is: 1.1.6 object-keys: 1.1.1 regexp.prototype.flags: 1.5.4 @@ -20423,7 +20574,7 @@ snapshots: '@adraffy/ens-normalize': 1.10.1 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 - '@types/node': 20.19.19 + '@types/node': 20.19.21 aes-js: 4.0.0-beta.5 tslib: 2.7.0 ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -21467,6 +21618,13 @@ snapshots: hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) strip-ansi: 6.0.1 + hardhat-contract-sizer@2.10.1(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): + dependencies: + chalk: 4.1.2 + cli-table3: 0.6.5 + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + strip-ansi: 6.0.1 + hardhat-deploy@0.11.45(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.8.0 @@ -21581,6 +21739,18 @@ snapshots: transitivePeerDependencies: - supports-color + hardhat-secure-accounts@1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): + dependencies: + '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) + debug: 4.4.3(supports-color@9.4.0) + enquirer: 2.4.1 + ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + lodash.clonedeep: 4.5.0 + prompt-sync: 4.2.0 + transitivePeerDependencies: + - supports-color + hardhat-secure-accounts@1.0.5(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@8.10.2(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -21598,6 +21768,11 @@ snapshots: console-table-printer: 2.14.6 hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat-storage-layout@0.1.7(hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): + dependencies: + console-table-printer: 2.14.6 + hardhat: 2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.21)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat@2.26.3(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10): dependencies: '@ethereumjs/util': 9.1.0 @@ -21900,7 +22075,7 @@ snapshots: http-response-object@3.0.2: dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 http-signature@1.2.0: dependencies: @@ -22816,12 +22991,12 @@ snapshots: transitivePeerDependencies: - enquirer - lint-staged@16.2.3: + lint-staged@16.2.4: dependencies: commander: 14.0.1 listr2: 9.0.4 micromatch: 4.0.8 - nano-spawn: 1.0.3 + nano-spawn: 2.0.0 pidtree: 0.6.0 string-argv: 0.3.2 yaml: 2.8.1 @@ -23822,7 +23997,7 @@ snapshots: nano-json-stream-parser@0.1.2: optional: true - nano-spawn@1.0.3: {} + nano-spawn@2.0.0: {} nanomatch@1.2.13: dependencies: @@ -24201,7 +24376,7 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.3)(zod@4.1.11) + abitype: 1.1.1(typescript@5.9.3)(zod@4.1.11) eventemitter3: 5.0.1 optionalDependencies: typescript: 5.9.3 @@ -26273,7 +26448,7 @@ snapshots: dependencies: '@types/concat-stream': 1.6.1 '@types/form-data': 0.0.33 - '@types/node': 20.19.19 + '@types/node': 20.19.21 '@types/qs': 6.14.0 caseless: 0.12.0 concat-stream: 1.6.2 @@ -26601,12 +26776,12 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -26660,6 +26835,8 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 + undici@6.22.0: {} + unfetch@4.2.0: {} unicorn-magic@0.1.0: {} @@ -26829,7 +27006,7 @@ snapshots: web3-bzz@1.2.11(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 got: 9.6.0 swarm-js: 0.1.42(bufferutil@4.0.9)(utf-8-validate@5.0.10) underscore: 1.9.1 @@ -26882,7 +27059,7 @@ snapshots: web3-core@1.2.11: dependencies: '@types/bn.js': 4.11.6 - '@types/node': 20.19.19 + '@types/node': 20.19.21 bignumber.js: 9.3.1 web3-core-helpers: 1.2.11 web3-core-method: 1.2.11 @@ -26954,7 +27131,7 @@ snapshots: web3-eth-personal@1.2.11: dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 web3-core: 1.2.11 web3-core-helpers: 1.2.11 web3-core-method: 1.2.11 @@ -27210,7 +27387,7 @@ snapshots: wkx@0.5.0: dependencies: - '@types/node': 20.19.19 + '@types/node': 20.19.21 wonka@4.0.15: {}