Skip to content

Commit

Permalink
feat: remove transfer tools from horizon
Browse files Browse the repository at this point in the history
Signed-off-by: Tomás Migone <[email protected]>
  • Loading branch information
tmigone committed Oct 1, 2024
1 parent f86b12a commit 24129d1
Show file tree
Hide file tree
Showing 6 changed files with 6 additions and 422 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
pragma solidity 0.8.27;

import { IRewardsIssuer } from "@graphprotocol/contracts/contracts/rewards/IRewardsIssuer.sol";
import { IL2StakingBase } from "@graphprotocol/contracts/contracts/l2/staking/IL2StakingBase.sol";

/**
* @title Interface for {HorizonStakingExtension} contract.
* @notice Provides functions for managing legacy allocations and transfer tools.
* @notice Provides functions for managing legacy allocations.
*/
interface IHorizonStakingExtension is IRewardsIssuer, IL2StakingBase {
interface IHorizonStakingExtension is IRewardsIssuer {
/**
* @dev Allocate GRT tokens for the purpose of serving queries of a subgraph deployment
* An allocation is created in the allocate() function and closed in closeAllocation()
Expand Down Expand Up @@ -39,15 +38,6 @@ interface IHorizonStakingExtension is IRewardsIssuer, IL2StakingBase {
Closed
}

/**
* @notice Emitted when a delegator delegates through the Graph Token Gateway using the transfer tools.
* @dev TODO(after transfer tools): delete
* @param serviceProvider The address of the service provider.
* @param delegator The address of the delegator.
* @param tokens The amount of tokens delegated.
*/
event StakeDelegated(address indexed serviceProvider, address indexed delegator, uint256 tokens, uint256 shares);

/**
* @dev Emitted when `indexer` close an allocation in `epoch` for `allocationID`.
* An amount of `tokens` get unallocated from `subgraphDeploymentID`.
Expand Down Expand Up @@ -87,15 +77,6 @@ interface IHorizonStakingExtension is IRewardsIssuer, IL2StakingBase {
uint256 delegationRewards
);

event CounterpartStakingAddressSet(address indexed counterpart);

/**
* @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 Close an allocation and free the staked tokens.
* To be eligible for rewards a proof of indexing must be presented.
Expand Down
120 changes: 3 additions & 117 deletions packages/horizon/contracts/staking/HorizonStakingExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ pragma solidity 0.8.27;
import { ICuration } from "@graphprotocol/contracts/contracts/curation/ICuration.sol";
import { IGraphToken } from "@graphprotocol/contracts/contracts/token/IGraphToken.sol";
import { IHorizonStakingExtension } from "../interfaces/internal/IHorizonStakingExtension.sol";
import { IHorizonStakingTypes } from "../interfaces/internal/IHorizonStakingTypes.sol";
import { IL2StakingTypes } from "@graphprotocol/contracts/contracts/l2/staking/IL2StakingTypes.sol";
import { IL2StakingBase } from "@graphprotocol/contracts/contracts/l2/staking/IL2StakingBase.sol";

import { TokenUtils } from "@graphprotocol/contracts/contracts/utils/TokenUtils.sol";
import { MathUtils } from "../libraries/MathUtils.sol";
Expand All @@ -22,12 +19,12 @@ import { HorizonStakingBase } from "./HorizonStakingBase.sol";
* to the Horizon Staking contract. It allows indexers to close allocations and collect pending query fees, but it
* does not allow for the creation of new allocations. This should allow indexers to migrate to a subgraph data service
* without losing rewards or having service interruptions.
* @dev TODO: Once the transition period and the transfer tools are deemed not necessary this contract
* can be removed. It's expected the transition period to last for a full allocation cycle (28 epochs).
* @dev TODO: Once the transition period passes this contract can be removed. It's expected the transition period to
* last for a full allocation cycle (28 epochs).
* @custom:security-contact Please email [email protected] if you find any
* bugs. We may have an active bug bounty program.
*/
contract HorizonStakingExtension is HorizonStakingBase, IL2StakingBase, IHorizonStakingExtension {
contract HorizonStakingExtension is HorizonStakingBase, IHorizonStakingExtension {
using TokenUtils for IGraphToken;
using PPMMath for uint256;

Expand All @@ -53,52 +50,6 @@ contract HorizonStakingExtension is HorizonStakingBase, IL2StakingBase, IHorizon
address subgraphDataServiceAddress
) HorizonStakingBase(controller, subgraphDataServiceAddress) {}

/**
* @notice Receive tokens with a callhook from the bridge.
* @dev The encoded _data can contain information about an service provider's stake
* or a delegator's delegation.
* See L1MessageCodes in IL2Staking for the supported messages.
* @dev "indexer" in this context refers to a service provider (legacy terminology for the bridge)
* @param from Token sender in L1
* @param tokens Amount of tokens that were transferred
* @param data ABI-encoded callhook data which must include a uint8 code and either a ReceiveIndexerStakeData or ReceiveDelegationData struct.
*/
function onTokenTransfer(
address from,
uint256 tokens,
bytes calldata data
) external override notPaused onlyL2Gateway {
require(from == _counterpartStakingAddress, "ONLY_L1_STAKING_THROUGH_BRIDGE");
(uint8 code, bytes memory functionData) = abi.decode(data, (uint8, bytes));

if (code == uint8(IL2StakingTypes.L1MessageCodes.RECEIVE_INDEXER_STAKE_CODE)) {
IL2StakingTypes.ReceiveIndexerStakeData memory indexerData = abi.decode(
functionData,
(IL2StakingTypes.ReceiveIndexerStakeData)
);
_receiveIndexerStake(tokens, indexerData);
} else if (code == uint8(IL2StakingTypes.L1MessageCodes.RECEIVE_DELEGATION_CODE)) {
IL2StakingTypes.ReceiveDelegationData memory delegationData = abi.decode(
functionData,
(IL2StakingTypes.ReceiveDelegationData)
);
_receiveDelegation(tokens, delegationData);
} else {
revert("INVALID_CODE");
}
}

/**
* @notice Set the address of the counterpart (L1 or L2) staking contract.
* @dev This function can only be called by the governor.
* TODO: Remove after L2 transition period
* @param counterpart Address of the counterpart staking contract in the other chain, without any aliasing.
*/
function setCounterpartStakingAddress(address counterpart) external override onlyGovernor {
_counterpartStakingAddress = counterpart;
emit CounterpartStakingAddressSet(counterpart);
}

/**
* @notice Close an allocation and free the staked tokens.
* To be eligible for rewards a proof of indexing must be presented.
Expand Down Expand Up @@ -318,71 +269,6 @@ contract HorizonStakingExtension is HorizonStakingBase, IL2StakingBase, IHorizon
return _legacyOperatorAuth[serviceProvider][operator];
}

/**
* @dev 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 _tokens Amount of tokens that were transferred
* @param _indexerData struct containing the indexer's address
*/
function _receiveIndexerStake(
uint256 _tokens,
IL2StakingTypes.ReceiveIndexerStakeData memory _indexerData
) internal {
address indexer = _indexerData.indexer;
// Deposit tokens into the indexer stake
_stake(indexer, _tokens);
}

/**
* @dev 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.
* @dev Note that L1 staking contract only allows delegation transfer if the indexer has already transferred,
* this means the corresponding delegation pool exists.
* @param _tokens Amount of tokens that were transferred
* @param _delegationData struct containing the delegator's address and the indexer's address
*/
function _receiveDelegation(
uint256 _tokens,
IL2StakingTypes.ReceiveDelegationData memory _delegationData
) internal {
require(_provisions[_delegationData.indexer][SUBGRAPH_DATA_SERVICE_ADDRESS].createdAt != 0, "!provision");
// Get the delegation pool of the indexer
DelegationPoolInternal storage pool = _legacyDelegationPools[_delegationData.indexer];
IHorizonStakingTypes.DelegationInternal storage delegation = pool.delegators[_delegationData.delegator];

// If pool is in an invalid state, return the tokens to the delegator
if (pool.tokens == 0 && (pool.shares != 0 || pool.sharesThawing != 0)) {
_graphToken().transfer(_delegationData.delegator, _tokens);
emit TransferredDelegationReturnedToDelegator(_delegationData.indexer, _delegationData.delegator, _tokens);
return;
}

// Calculate shares to issue (without applying any delegation tax)
uint256 shares = (pool.tokens == 0 || pool.tokens == pool.tokensThawing)
? _tokens
: ((_tokens * pool.shares) / (pool.tokens - pool.tokensThawing));

if (shares == 0 || _tokens < MINIMUM_DELEGATION) {
// If no shares would be issued (probably a rounding issue or attack),
// or if the amount is under the minimum delegation (which could be part of a rounding attack),
// return the tokens to the delegator
_graphToken().pushTokens(_delegationData.delegator, _tokens);
emit TransferredDelegationReturnedToDelegator(_delegationData.indexer, _delegationData.delegator, _tokens);
} else {
// Update the delegation pool
pool.tokens = pool.tokens + _tokens;
pool.shares = pool.shares + shares;

// Update the individual delegation
delegation.shares = delegation.shares + shares;

emit StakeDelegated(_delegationData.indexer, _delegationData.delegator, _tokens, shares);
}
}

/**
* @dev Collect tax to burn for an amount of tokens.
* @param _tokens Total tokens received used to calculate the amount of tax to collect
Expand Down
6 changes: 1 addition & 5 deletions packages/horizon/test/GraphBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,8 @@ abstract contract GraphBaseTest is IHorizonStakingTypes, Utils, Constants {

address subgraphDataServiceLegacyAddress = makeAddr("subgraphDataServiceLegacyAddress");
address subgraphDataServiceAddress = makeAddr("subgraphDataServiceAddress");

// We use these addresses to mock calls from the counterpart staking contract

address graphTokenGatewayAddress = makeAddr("GraphTokenGateway");
address counterpartStaking = makeAddr("counterpartStaking");

/* Users */

Expand Down Expand Up @@ -188,8 +186,6 @@ abstract contract GraphBaseTest is IHorizonStakingTypes, Utils, Constants {
proxyAdmin.upgrade(stakingProxy, address(stakingBase));
proxyAdmin.acceptProxy(stakingBase, stakingProxy);
staking = IHorizonStaking(address(stakingProxy));

staking.setCounterpartStakingAddress(address(counterpartStaking));
}

function setupProtocol() private {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1228,107 +1228,6 @@ abstract contract HorizonStakingSharedTest is GraphBaseTest {
assertEq(afterMaxThawingPeriod, maxThawingPeriod);
}

function _setCounterpartStakingAddress(address counterpartStakingAddress) internal {
// setCounterpartStakingAddress
vm.expectEmit(address(staking));
emit IHorizonStakingExtension.CounterpartStakingAddressSet(counterpartStakingAddress);
staking.setCounterpartStakingAddress(counterpartStakingAddress);

// after
address afterCounterpartStakingAddress = _getStorage_CounterpartStakingAddress();

// assert
assertEq(afterCounterpartStakingAddress, counterpartStakingAddress);
}

struct BeforeValues_ReceiveDelegation {
DelegationPoolInternalTest pool;
DelegationInternal delegation;
uint256 delegatedTokens;
uint256 stakingBalance;
uint256 delegatorBalance;
}
function _onTokenTransfer_ReceiveDelegation(address from, uint256 tokens, bytes memory data) internal {
address serviceProvider;
address delegator;
{
(, bytes memory fnData) = abi.decode(data, (uint8, bytes));
(serviceProvider, delegator) = abi.decode(fnData, (address, address));
}

// before
BeforeValues_ReceiveDelegation memory beforeValues;
beforeValues.pool = _getStorage_DelegationPoolInternal(serviceProvider, subgraphDataServiceLegacyAddress, true);
beforeValues.delegation = _getStorage_Delegation(
serviceProvider,
subgraphDataServiceLegacyAddress,
delegator,
true
);
beforeValues.stakingBalance = token.balanceOf(address(staking));
beforeValues.delegatorBalance = token.balanceOf(delegator);
beforeValues.delegatedTokens = staking.getDelegatedTokensAvailable(
serviceProvider,
subgraphDataServiceLegacyAddress
);

// calc
uint256 calcShares = (beforeValues.pool.tokens == 0 || beforeValues.pool.tokens == beforeValues.pool.tokensThawing)
? tokens
: ((tokens * beforeValues.pool.shares) / (beforeValues.pool.tokens - beforeValues.pool.tokensThawing));

bool earlyExit = (calcShares == 0 || tokens < 1 ether) ||
(beforeValues.pool.tokens == 0 && (beforeValues.pool.shares != 0 || beforeValues.pool.sharesThawing != 0));

// onTokenTransfer
if (earlyExit) {
vm.expectEmit();
emit Transfer(address(staking), delegator, tokens);
vm.expectEmit();
emit IL2StakingBase.TransferredDelegationReturnedToDelegator(serviceProvider, delegator, tokens);
} else {
vm.expectEmit();
emit IHorizonStakingExtension.StakeDelegated(serviceProvider, delegator, tokens, calcShares);
}
staking.onTokenTransfer(from, tokens, data);

// after
DelegationPoolInternalTest memory afterPool = _getStorage_DelegationPoolInternal(serviceProvider, subgraphDataServiceLegacyAddress, true);
DelegationInternal memory afterDelegation = _getStorage_Delegation(
serviceProvider,
subgraphDataServiceLegacyAddress,
delegator,
true
);
uint256 afterDelegatedTokens = staking.getDelegatedTokensAvailable(
serviceProvider,
subgraphDataServiceLegacyAddress
);
uint256 afterDelegatorBalance = token.balanceOf(delegator);
uint256 afterStakingBalance = token.balanceOf(address(staking));

// assertions
if (earlyExit) {
assertEq(beforeValues.pool.tokens, afterPool.tokens);
assertEq(beforeValues.pool.shares, afterPool.shares);
assertEq(beforeValues.pool.tokensThawing, afterPool.tokensThawing);
assertEq(beforeValues.pool.sharesThawing, afterPool.sharesThawing);
assertEq(0, afterDelegation.shares - beforeValues.delegation.shares);
assertEq(beforeValues.delegatedTokens, afterDelegatedTokens);
assertEq(beforeValues.delegatorBalance + tokens, afterDelegatorBalance);
assertEq(beforeValues.stakingBalance - tokens, afterStakingBalance);
} else {
assertEq(beforeValues.pool.tokens + tokens, afterPool.tokens);
assertEq(beforeValues.pool.shares + calcShares, afterPool.shares);
assertEq(beforeValues.pool.tokensThawing, afterPool.tokensThawing);
assertEq(beforeValues.pool.sharesThawing, afterPool.sharesThawing);
assertEq(calcShares, afterDelegation.shares - beforeValues.delegation.shares);
assertEq(beforeValues.delegatedTokens + tokens, afterDelegatedTokens);
assertEq(beforeValues.delegatorBalance, afterDelegatorBalance);
assertEq(beforeValues.stakingBalance, afterStakingBalance);
}
}

struct BeforeValues_Slash {
Provision provision;
DelegationPoolInternalTest pool;
Expand Down
12 changes: 0 additions & 12 deletions packages/horizon/test/staking/governance/governance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,4 @@ contract HorizonStakingGovernanceTest is HorizonStakingTest {
vm.expectRevert(expectedError);
staking.setMaxThawingPeriod(MAX_THAWING_PERIOD);
}

function testGovernance_SetCounterpartStakingAddress(address counterpartStakingAddress) public useGovernor {
_setCounterpartStakingAddress(counterpartStakingAddress);
}

function testGovernance_RevertWhen_SetCounterpartStakingAddress_NotGovernor(
address counterpartStakingAddress
) public useIndexer {
bytes memory expectedError = abi.encodeWithSignature("ManagedOnlyGovernor()");
vm.expectRevert(expectedError);
staking.setCounterpartStakingAddress(counterpartStakingAddress);
}
}
Loading

0 comments on commit 24129d1

Please sign in to comment.