Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: set and hardcode minimum L2 curation to 1 GRT, enforce it in all cases, allow sub-epoch collect (OZ H-01) #874

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions contracts/curation/Curation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -362,16 +362,16 @@ contract Curation is CurationV2Storage, GraphUpgradeable {
// Init curation pool
if (curationPool.tokens == 0) {
require(
_tokensIn >= minimumCurationDeposit,
_tokensIn >= __minimumCurationDeposit,
"Curation deposit is below minimum required"
);
return
BancorFormula(bondingCurve)
.calculatePurchaseReturn(
SIGNAL_PER_MINIMUM_DEPOSIT,
minimumCurationDeposit,
__minimumCurationDeposit,
defaultReserveRatio,
_tokensIn.sub(minimumCurationDeposit)
_tokensIn.sub(__minimumCurationDeposit)
)
.add(SIGNAL_PER_MINIMUM_DEPOSIT);
}
Expand Down Expand Up @@ -417,6 +417,14 @@ contract Curation is CurationV2Storage, GraphUpgradeable {
);
}

/**
* @notice Getter for minimum curation deposit
* @return Minimum amount of tokens that must be deposited to mint signal
*/
function minimumCurationDeposit() external view returns (uint256) {
return __minimumCurationDeposit;
}

/**
* @dev Internal: Set the default reserve ratio percentage for a curation pool.
* @notice Update the default reserver ratio to `_defaultReserveRatio`
Expand All @@ -442,7 +450,7 @@ contract Curation is CurationV2Storage, GraphUpgradeable {
function _setMinimumCurationDeposit(uint256 _minimumCurationDeposit) private {
require(_minimumCurationDeposit != 0, "Minimum curation deposit cannot be 0");

minimumCurationDeposit = _minimumCurationDeposit;
__minimumCurationDeposit = _minimumCurationDeposit;
emit ParameterUpdated("minimumCurationDeposit");
}

Expand Down
7 changes: 4 additions & 3 deletions contracts/curation/CurationStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ abstract contract CurationV1Storage is Managed, ICuration {
/// @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
/// @dev This is the `startPoolBalance` for the bonding curve
uint256 public minimumCurationDeposit;
/// @dev Minimum amount allowed to be deposited by curators to initialize a pool
/// In L2Curation, this is now replaced by the MINIMUM_CURATION_DEPOSIT constant.
/// So this is now only used in Curation in L1.
uint256 internal __minimumCurationDeposit;

/// Bonding curve library
/// Unused in L2.
Expand Down
68 changes: 32 additions & 36 deletions contracts/l2/curation/L2Curation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
/// @dev 100% in parts per million
uint32 private constant MAX_PPM = 1000000;

/// @dev Minimum amount of tokens that must be deposited to mint signal
uint256 private constant MINIMUM_CURATION_DEPOSIT = 1e18;

/// @dev Amount of signal you get with your minimum token deposit
uint256 private constant SIGNAL_PER_MINIMUM_DEPOSIT = 1; // 1e-18 signal as 18 decimal number
uint256 private constant SIGNAL_PER_MINIMUM_DEPOSIT = 1e18;

/// @dev Reserve ratio for all subgraphs set to 100% for a flat bonding curve
uint32 private immutable fixedReserveRatio = MAX_PPM;
Expand Down Expand Up @@ -85,21 +88,19 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
* @param _controller Controller contract that manages this contract
* @param _curationTokenMaster Address of the GraphCurationToken master copy
* @param _curationTaxPercentage Percentage of curation tax to be collected
* @param _minimumCurationDeposit Minimum amount of tokens that can be deposited as curation signal
*/
function initialize(
address _controller,
address _curationTokenMaster,
uint32 _curationTaxPercentage,
uint256 _minimumCurationDeposit
uint256 // _minimumCurationDeposit, not used in L2
) external onlyImpl initializer {
Managed._initialize(_controller);

// For backwards compatibility:
defaultReserveRatio = fixedReserveRatio;
emit ParameterUpdated("defaultReserveRatio");
_setCurationTaxPercentage(_curationTaxPercentage);
_setMinimumCurationDeposit(_minimumCurationDeposit);
_setCurationTokenMaster(_curationTokenMaster);
}

Expand All @@ -111,19 +112,6 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
revert("Not implemented in L2");
}

/**
* @dev Set the minimum deposit amount for curators.
* @notice Update the minimum deposit amount to `_minimumCurationDeposit`
* @param _minimumCurationDeposit Minimum amount of tokens required deposit
*/
function setMinimumCurationDeposit(uint256 _minimumCurationDeposit)
external
override
onlyGovernor
{
_setMinimumCurationDeposit(_minimumCurationDeposit);
}

/**
* @notice Set the curation tax percentage to charge when a curator deposits GRT tokens.
* @param _percentage Curation tax percentage charged when depositing GRT tokens
Expand Down Expand Up @@ -296,17 +284,22 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
uint256 _tokensOutMin
) external override notPartialPaused returns (uint256) {
address curator = msg.sender;

uint256 previousCuratorSignal = getCuratorSignal(curator, _subgraphDeploymentID);
// Validations
require(_signalIn != 0, "Cannot burn zero signal");
require(
getCuratorSignal(curator, _subgraphDeploymentID) >= _signalIn,
"Cannot burn more signal than you own"
);
require(previousCuratorSignal >= _signalIn, "Cannot burn more signal than you own");

// Get the amount of tokens to refund based on returned signal
uint256 tokensOut = signalToTokens(_subgraphDeploymentID, _signalIn);

if (previousCuratorSignal > _signalIn) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pedantic nit, this could be previousCuratorSignal != _signalIn and save a little gas. Though not sure how you feel about code readability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but yeah for now I'll leave it like this for readability

// The user's remaining tokens can't be under the minimum curation,
// to prevent rounding attacks
uint256 remainingTokens = signalToTokens(
_subgraphDeploymentID,
previousCuratorSignal - _signalIn
);
require(remainingTokens >= MINIMUM_CURATION_DEPOSIT, "Less than minimum curation left");
}
// Slippage protection
require(tokensOut >= _tokensOutMin, "Slippage protection");

Expand Down Expand Up @@ -448,15 +441,18 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
}

/**
* @dev Internal: Set the minimum deposit amount for curators.
* Update the minimum deposit amount to `_minimumCurationDeposit`
* @param _minimumCurationDeposit Minimum amount of tokens required deposit
* @notice (Deprecated in L2) Update the minimum deposit amount to `_minimumCurationDeposit`
*/
function _setMinimumCurationDeposit(uint256 _minimumCurationDeposit) private {
require(_minimumCurationDeposit != 0, "Minimum curation deposit cannot be 0");
function setMinimumCurationDeposit(uint256) external view override onlyGovernor {
revert("Not implemented in L2");
}

minimumCurationDeposit = _minimumCurationDeposit;
emit ParameterUpdated("minimumCurationDeposit");
/**
* @notice Getter for minimum curation deposit
* @return Minimum amount of tokens that must be deposited to mint signal
*/
function minimumCurationDeposit() external view returns (uint256) {
return MINIMUM_CURATION_DEPOSIT;
}

/**
Expand Down Expand Up @@ -510,19 +506,19 @@ contract L2Curation is CurationV2Storage, GraphUpgradeable, IL2Curation {
view
returns (uint256)
{
require(
_tokensIn >= MINIMUM_CURATION_DEPOSIT,
"Curation deposit is below minimum required"
);
// Get curation pool tokens and signal
CurationPool memory curationPool = pools[_subgraphDeploymentID];

// Init curation pool
if (curationPool.tokens == 0) {
require(
_tokensIn >= minimumCurationDeposit,
"Curation deposit is below minimum required"
);
return
SIGNAL_PER_MINIMUM_DEPOSIT.add(
SIGNAL_PER_MINIMUM_DEPOSIT.mul(_tokensIn.sub(minimumCurationDeposit)).div(
minimumCurationDeposit
SIGNAL_PER_MINIMUM_DEPOSIT.mul(_tokensIn.sub(MINIMUM_CURATION_DEPOSIT)).div(
MINIMUM_CURATION_DEPOSIT
)
);
}
Expand Down
42 changes: 27 additions & 15 deletions contracts/l2/discovery/L2GNS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ import { IL2Curation } from "../curation/IL2Curation.sol";
contract L2GNS is GNS, L2GNSV1Storage, IL2GNS {
using SafeMathUpgradeable for uint256;

/// Offset added to subgraph IDs to calculate the L2 alias of L1 subgraph IDs
uint256 public constant SUBGRAPH_ID_ALIAS_OFFSET =
uint256(0x1111000000000000000000000000000000000000000000000000000000001111);

/// @dev Minimum curation deposit, must match the value in L2Curation
uint256 private constant MINIMUM_CURATION_DEPOSIT = 1e18;

/// @dev Emitted when a subgraph is received from L1 through the bridge
event SubgraphReceivedFromL1(
uint256 indexed _l1SubgraphID,
Expand All @@ -46,7 +50,8 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS {
uint256 _tokens
);
/// @dev 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.
/// This can happen if the subgraph transfer was not finished when the curator's tokens arrived,
/// or if the tokens were under the L2 minimum curation deposit
event CuratorBalanceReturnedToBeneficiary(
uint256 indexed _l1SubgraphID,
address indexed _l2Curator,
Expand Down Expand Up @@ -124,26 +129,27 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS {
IL2Curation curation = IL2Curation(address(curation()));
// Update pool: constant nSignal, vSignal can change (w/no slippage protection)
// Buy all signal from the new deployment
uint256 vSignal = curation.mintTaxFree(_subgraphDeploymentID, transferData.tokens);
uint256 nSignal = vSignalToNSignal(_l2SubgraphID, vSignal);
uint256 vSignal;
uint256 nSignal;
if (transferData.tokens >= MINIMUM_CURATION_DEPOSIT) {
// Update pool: constant nSignal, vSignal can change (w/no slippage protection)
// Buy all signal from the new deployment
vSignal = curation.mintTaxFree(_subgraphDeploymentID, transferData.tokens);
nSignal = vSignalToNSignal(_l2SubgraphID, vSignal);
subgraphData.vSignal = vSignal;
subgraphData.nSignal = nSignal;
subgraphData.curatorNSignal[msg.sender] = nSignal;
emit SignalMinted(_l2SubgraphID, msg.sender, nSignal, vSignal, transferData.tokens);
}

subgraphData.disabled = false;
subgraphData.vSignal = vSignal;
subgraphData.nSignal = nSignal;
subgraphData.curatorNSignal[msg.sender] = nSignal;
subgraphData.subgraphDeploymentID = _subgraphDeploymentID;
// Set the token metadata
_setSubgraphMetadata(_l2SubgraphID, _subgraphMetadata);

emit SubgraphPublished(_l2SubgraphID, _subgraphDeploymentID, fixedReserveRatio);
emit SubgraphUpgraded(
_l2SubgraphID,
subgraphData.vSignal,
transferData.tokens,
_subgraphDeploymentID
);
emit SubgraphUpgraded(_l2SubgraphID, vSignal, transferData.tokens, _subgraphDeploymentID);
emit SubgraphVersionUpdated(_l2SubgraphID, _subgraphDeploymentID, _versionMetadata);
emit SignalMinted(_l2SubgraphID, msg.sender, nSignal, vSignal, transferData.tokens);
emit SubgraphL2TransferFinalized(_l2SubgraphID);
}

Expand Down Expand Up @@ -248,7 +254,13 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS {
// The subgraph will be disabled until finishSubgraphTransferFromL1 is called
subgraphData.disabled = true;

transferData.tokens = _tokens;
if (_tokens >= MINIMUM_CURATION_DEPOSIT) {
transferData.tokens = _tokens;
} else {
// transferData.tokens stays uninitialized at zero
graphToken().transfer(_subgraphOwner, _tokens);
emit CuratorBalanceReturnedToBeneficiary(_l1SubgraphID, _subgraphOwner, _tokens);
}
transferData.subgraphReceivedOnL2BlockNumber = block.number;

// Mint the NFT. Use the subgraphID as tokenID.
Expand Down Expand Up @@ -279,7 +291,7 @@ contract L2GNS is GNS, L2GNSV1Storage, IL2GNS {
SubgraphData storage subgraphData = _getSubgraphData(l2SubgraphID);

// If subgraph transfer wasn't finished, we should send the tokens to the curator
if (!transferData.l2Done || subgraphData.disabled) {
if (!transferData.l2Done || subgraphData.disabled || _tokensIn < MINIMUM_CURATION_DEPOSIT) {
graphToken().transfer(_curator, _tokensIn);
emit CuratorBalanceReturnedToBeneficiary(_l1SubgraphID, _curator, _tokensIn);
} else {
Expand Down
8 changes: 2 additions & 6 deletions contracts/staking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -358,18 +358,14 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
AllocationState allocState = _getAllocationState(_allocationID);
require(allocState != AllocationState.Null, "!collect");

// Get allocation
Allocation storage alloc = __allocations[_allocationID];
// The allocation must've been opened at least 1 epoch ago,
// to prevent manipulation of the curation or delegation pools
require(alloc.createdAtEpoch < epochManager().currentEpoch(), "!epoch");

// If the query fees are zero, we don't want to revert
// but we also don't need to do anything, so just return
if (_tokens == 0) {
return;
}

// Get allocation
Allocation storage alloc = __allocations[_allocationID];
bytes32 subgraphDeploymentID = alloc.subgraphDeploymentID;

uint256 queryFees = _tokens; // Tokens collected from the channel
Expand Down
5 changes: 3 additions & 2 deletions e2e/deployment/config/l2/l2Curation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'chai'
import hre from 'hardhat'
import { getItemValue } from '../../../../cli/config'
import GraphChain from '../../../../gre/helpers/chain'
import { toGRT } from '../../../../cli/network'

describe('[L2] L2Curation configuration', () => {
const graph = hre.graph()
Expand Down Expand Up @@ -36,9 +37,9 @@ describe('[L2] L2Curation configuration', () => {
expect(value).eq(expected)
})

it('minimumCurationDeposit should match "minimumCurationDeposit" in the config file', async function () {
it('minimumCurationDeposit should match the hardcoded value', async function () {
const value = await L2Curation.minimumCurationDeposit()
const expected = getItemValue(graphConfig, 'contracts/L2Curation/init/minimumCurationDeposit')
const expected = toGRT('1')
expect(value).eq(expected)
})
})
4 changes: 4 additions & 0 deletions test/curation/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ describe('Curation:Config', () => {
.setMinimumCurationDeposit(defaults.curation.minimumCurationDeposit)
await expect(tx).revertedWith('Only Controller governor')
})

it('should get `minimumCurationDeposit`', async function () {
expect(await curation.minimumCurationDeposit()).eq(defaults.curation.minimumCurationDeposit)
})
})

describe('curationTaxPercentage', function () {
Expand Down
Loading