Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
51 changes: 25 additions & 26 deletions contracts/curation/Curation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ contract Curation is CurationV1Storage, GraphUpgradeable {

// Update curation pool
curationPool.tokens = curationPool.tokens.add(_tokensIn.sub(curationTax));
curationPool.gcs.mint(curator, signalOut);
curationPool.gcs.mint(curator, signalOut, _tokensIn.sub(curationTax));

emit Signalled(curator, _subgraphDeploymentID, _tokensIn, signalOut, curationTax);

Expand All @@ -269,14 +269,14 @@ contract Curation is CurationV1Storage, GraphUpgradeable {
* @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 override notPartialPaused returns (uint256) {
function burn(bytes32 _subgraphDeploymentID, uint256 _signalIn)
external
override
notPartialPaused
returns (uint256)
{
address curator = msg.sender;

// Validations
Expand All @@ -286,11 +286,9 @@ contract Curation is CurationV1Storage, GraphUpgradeable {
"Cannot burn more signal than you own"
);

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

// Slippage protection
require(tokensOut >= _tokensOutMin, "Slippage protection");
// Get the amount of tokens to refund based on the amount
// deposited by the curator and unsignal amount
uint256 tokensOut = signalToTokens(_subgraphDeploymentID, _signalIn, curator);

// Trigger update rewards calculation
_updateRewards(_subgraphDeploymentID);
Expand Down Expand Up @@ -433,30 +431,31 @@ contract Curation is CurationV1Storage, GraphUpgradeable {
* @param _signalIn Amount of signal to burn
* @return Amount of tokens to get for an amount of signal
*/
function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn)
public
view
override
returns (uint256)
{
function signalToTokens(
bytes32 _subgraphDeploymentID,
uint256 _signalIn,
address _curator
) public view override returns (uint256) {
CurationPool memory curationPool = pools[_subgraphDeploymentID];
uint256 curationPoolSignal = getCurationPoolSignal(_subgraphDeploymentID);
require(
curationPool.tokens > 0,
"Subgraph deployment must be curated to perform calculations"
);
uint256 curationPoolSignal = getCurationPoolSignal(_subgraphDeploymentID);
require(
curationPoolSignal >= _signalIn,
"Signal must be above or equal to signal issued in the curation pool"
);
uint256 curatorSignal = getCuratorSignal(_curator, _subgraphDeploymentID);
uint256 poolTokens = curationPool.tokens;

return
BancorFormula(bondingCurve).calculateSaleReturn(
curationPoolSignal,
curationPool.tokens,
curationPool.reserveRatio,
_signalIn
);
uint256 royaltiesOf = poolTokens
.sub(curationPool.gcs.totalDeposited())
.mul(curatorSignal)
.div(curationPoolSignal);
uint256 grtValueOf = curationPool.gcs.grtValueOf(_curator, _signalIn);

return grtValueOf.add(royaltiesOf);
}

/**
Expand Down
38 changes: 37 additions & 1 deletion contracts/curation/GraphCurationToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pragma solidity ^0.7.6;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";

import "../governance/Governed.sol";
Expand All @@ -19,6 +20,13 @@ import "../governance/Governed.sol";
* gas-saving purposes.
*/
contract GraphCurationToken is ERC20Upgradeable, Governed {
using SafeMath for uint256;

// Bookkeeping for GRT deposits
mapping(address => uint256) public deposits;

uint256 public totalDeposited;

/**
* @dev Graph Curation Token Contract initializer.
* @param _owner Address of the contract issuing this token
Expand All @@ -32,9 +40,16 @@ contract GraphCurationToken is ERC20Upgradeable, Governed {
* @dev Mint new tokens.
* @param _to Address to send the newly minted tokens
* @param _amount Amount of tokens to mint
* @param _grtDeposit Amount of GRT deposited to mint the GCS
*/
function mint(address _to, uint256 _amount) public onlyGovernor {
function mint(
address _to,
uint256 _amount,
uint256 _grtDeposit
) public onlyGovernor {
_mint(_to, _amount);
deposits[_to] = deposits[_to].add(_grtDeposit);
totalDeposited = totalDeposited.add(_grtDeposit);
}

/**
Expand All @@ -43,6 +58,27 @@ contract GraphCurationToken is ERC20Upgradeable, Governed {
* @param _amount Amount of tokens to burn
*/
function burnFrom(address _account, uint256 _amount) public onlyGovernor {
uint256 delta = grtValueOf(_account, _amount);
_burn(_account, _amount);
deposits[_account] = deposits[_account].sub(delta);
totalDeposited = totalDeposited.sub(delta);
}

/**
* @dev Transfer tokens from the sender to an address.
* @param _recipient Address to where tokens will be sent
* @param _amount Amount of tokens to burn
*/
function transfer(address _recipient, uint256 _amount) public virtual override returns (bool) {
uint256 depositDelta = grtValueOf(msg.sender, _amount);
_transfer(msg.sender, _recipient, _amount);
deposits[msg.sender] = deposits[msg.sender].sub(depositDelta);
deposits[_recipient] = deposits[_recipient].add(depositDelta);
return true;
}

function grtValueOf(address _account, uint256 _amount) public view returns (uint256) {
return
balanceOf(_account) == 0 ? 0 : deposits[_account].mul(_amount).div(balanceOf(_account));
}
}
5 changes: 2 additions & 3 deletions contracts/curation/ICuration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ interface ICuration {

function burn(
bytes32 _subgraphDeploymentID,
uint256 _signalIn,
uint256 _tokensOutMin
uint256 _signalIn
) external returns (uint256);

function collect(bytes32 _subgraphDeploymentID, uint256 _tokens) external;
Expand All @@ -49,7 +48,7 @@ interface ICuration {
view
returns (uint256, uint256);

function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn)
function signalToTokens(bytes32 _subgraphDeploymentID, uint256 _signalIn, address _curator)
external
view
returns (uint256);
Expand Down
6 changes: 5 additions & 1 deletion contracts/curation/IGraphCurationToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ interface IGraphCurationToken is IERC20Upgradeable {

function burnFrom(address _account, uint256 _amount) external;

function mint(address _to, uint256 _amount) external;
function mint(address _to, uint256 _amount, uint256 _grtDeposit) external;

function grtValueOf(address _account, uint256 _amount) external view returns (uint256);

function totalDeposited() external view returns (uint256);
}
16 changes: 6 additions & 10 deletions contracts/discovery/GNS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,11 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
// Move all signal from previous version to new version
// NOTE: We will only do this as long as there is signal on the subgraph
if (subgraphData.nSignal > 0) {
// Burn all version signal in the name pool for tokens (w/no slippage protection)
// Burn all version signal in the name pool for tokens
// Sell all signal from the old deployment
uint256 tokens = curation.burn(
subgraphData.subgraphDeploymentID,
subgraphData.vSignal,
0
subgraphData.vSignal
);

// Take the owner cut of the curation tax, add it to the total
Expand Down Expand Up @@ -367,8 +366,7 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
if (subgraphData.nSignal > 0) {
subgraphData.withdrawableGRT = curation().burn(
subgraphData.subgraphDeploymentID,
subgraphData.vSignal,
0
subgraphData.vSignal
);
}

Expand Down Expand Up @@ -422,12 +420,10 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
* @dev 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
uint256 _nSignal
) external override notPartialPaused {
// Subgraph checks
SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID);
Expand All @@ -442,7 +438,7 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {

// Get tokens for name signal amount to burn
uint256 vSignal = nSignalToVSignal(_subgraphID, _nSignal);
uint256 tokens = curation().burn(subgraphData.subgraphDeploymentID, vSignal, _tokensOutMin);
uint256 tokens = curation().burn(subgraphData.subgraphDeploymentID, vSignal);

// Update pools
subgraphData.vSignal = subgraphData.vSignal.sub(vSignal);
Expand Down Expand Up @@ -569,7 +565,7 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
// It does not make sense to convert signal from a disabled or non-existing one
SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID);
uint256 vSignal = nSignalToVSignal(_subgraphID, _nSignalIn);
uint256 tokensOut = curation().signalToTokens(subgraphData.subgraphDeploymentID, vSignal);
uint256 tokensOut = curation().signalToTokens(subgraphData.subgraphDeploymentID, vSignal, address(this));
return (vSignal, tokensOut);
}

Expand Down
3 changes: 1 addition & 2 deletions contracts/discovery/IGNS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ interface IGNS {

function burnSignal(
uint256 _subgraphID,
uint256 _nSignal,
uint256 _tokensOutMin
uint256 _nSignal
) external;

function withdraw(uint256 _subgraphID) external;
Expand Down
32 changes: 11 additions & 21 deletions test/curation/curation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('Curation', () => {
const beforeTotalTokens = await grt.balanceOf(curation.address)

// Redeem
const tx = curation.connect(curator.signer).burn(subgraphDeploymentID, signalToRedeem, 0)
const tx = curation.connect(curator.signer).burn(subgraphDeploymentID, signalToRedeem)
await expect(tx)
.emit(curation, 'Burned')
.withArgs(curator.address, subgraphDeploymentID, expectedTokens, signalToRedeem)
Expand Down Expand Up @@ -212,7 +212,7 @@ describe('Curation', () => {
const tokensToDeposit = curatorTokens

it('reject convert signal to tokens if subgraph deployment not initted', async function () {
const tx = curation.signalToTokens(subgraphDeploymentID, toGRT('100'))
const tx = curation.signalToTokens(subgraphDeploymentID, toGRT('100'), curator.address)
await expect(tx).revertedWith('Subgraph deployment must be curated to perform calculations')
})

Expand All @@ -222,7 +222,7 @@ describe('Curation', () => {

// Conversion
const signal = await curation.getCurationPoolSignal(subgraphDeploymentID)
const expectedTokens = await curation.signalToTokens(subgraphDeploymentID, signal)
const expectedTokens = await curation.signalToTokens(subgraphDeploymentID, signal, curator.address)
expect(expectedTokens).eq(tokensToDeposit)
})

Expand All @@ -241,7 +241,7 @@ describe('Curation', () => {

// Conversion
const signal = await curation.getCurationPoolSignal(subgraphDeploymentID)
const tokens = await curation.signalToTokens(subgraphDeploymentID, signal)
const tokens = await curation.signalToTokens(subgraphDeploymentID, signal, curator.address)
expect(tokens).eq(tokensToDeposit.sub(expectedCurationTax))
expect(expectedCurationTax).eq(curationTax)
})
Expand Down Expand Up @@ -377,7 +377,7 @@ describe('Curation', () => {
).sub(signalOutRemainder)
const tx1 = await curation
.connect(curator.signer)
.burn(subgraphDeploymentID, signalOutPartial, 0)
.burn(subgraphDeploymentID, signalOutPartial)
const r1 = await tx1.wait()
const event1 = curation.interface.parseLog(r1.events[2]).args
const tokensOut1 = event1.tokens
Expand All @@ -388,7 +388,7 @@ describe('Curation', () => {
// Unsignal the rest
const tx2 = await curation
.connect(curator.signer)
.burn(subgraphDeploymentID, signalOutRemainder, 0)
.burn(subgraphDeploymentID, signalOutRemainder)
const r2 = await tx2.wait()
const event2 = curation.interface.parseLog(r2.events[2]).args
const tokensOut2 = event2.tokens
Expand All @@ -404,19 +404,19 @@ describe('Curation', () => {
})

it('reject redeem more than a curator owns', async function () {
const tx = curation.connect(me.signer).burn(subgraphDeploymentID, toGRT('1'), 0)
const tx = curation.connect(me.signer).burn(subgraphDeploymentID, toGRT('1'))
await expect(tx).revertedWith('Cannot burn more signal than you own')
})

it('reject redeem zero signal', async function () {
const tx = curation.connect(me.signer).burn(subgraphDeploymentID, toGRT('0'), 0)
const tx = curation.connect(me.signer).burn(subgraphDeploymentID, toGRT('0'))
await expect(tx).revertedWith('Cannot burn zero signal')
})

it('should allow to redeem *partially*', async function () {
// Redeem just one signal
const signalToRedeem = toGRT('1')
const expectedTokens = toGRT('532.455532033675866536')
const expectedTokens = toGRT('316.227766016837933299')
await shouldBurn(signalToRedeem, expectedTokens)
})

Expand All @@ -431,7 +431,7 @@ describe('Curation', () => {
// Redeem "almost" all signal
const signal = await curation.getCuratorSignal(curator.address, subgraphDeploymentID)
const signalToRedeem = signal.sub(toGRT('0.000001'))
const expectedTokens = await curation.signalToTokens(subgraphDeploymentID, signalToRedeem)
const expectedTokens = await curation.signalToTokens(subgraphDeploymentID, signalToRedeem, curator.address)
await shouldBurn(signalToRedeem, expectedTokens)

// The pool should have less tokens that required by minimumCurationDeposit
Expand All @@ -447,16 +447,6 @@ describe('Curation', () => {
await shouldMint(tokensToDeposit, expectedSignal)
})

it('should revert redeem if over slippage', async function () {
const signalToRedeem = await curation.getCuratorSignal(curator.address, subgraphDeploymentID)
const expectedTokens = tokensToDeposit

const tx = curation
.connect(curator.signer)
.burn(subgraphDeploymentID, signalToRedeem, expectedTokens.add(1))
await expect(tx).revertedWith('Slippage protection')
})

it('should not re-deploy the curation token when signal is reset', async function () {
const beforeSubgraphPool = await curation.pools(subgraphDeploymentID)

Expand Down Expand Up @@ -497,7 +487,7 @@ describe('Curation', () => {
for (const signalToRedeem of chunkify(totalSignal, 10)) {
const tx = await curation
.connect(curator.signer)
.burn(subgraphDeploymentID, signalToRedeem, 0)
.burn(subgraphDeploymentID, signalToRedeem)
const receipt = await tx.wait()
const event: Event = receipt.events.pop()
const tokens = event.args['tokens']
Expand Down
Loading