Skip to content

feat: make GNS signal transferable #568

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

Merged
merged 2 commits into from
Jul 27, 2022
Merged
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
47 changes: 43 additions & 4 deletions contracts/discovery/GNS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
uint256 tokensReceived
);

/**
* @dev Emitted when a curator transfers signal.
*/
event SignalTransferred(
uint256 indexed subgraphID,
address indexed from,
address indexed to,
uint256 nSignalTransferred
);

/**
* @dev Emitted when a subgraph is created.
*/
Expand Down Expand Up @@ -202,10 +212,9 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
* @param _subgraphNFT Address of the ERC721 contract
*/
function _setSubgraphNFT(address _subgraphNFT) private {
require(
_subgraphNFT != address(0) && Address.isContract(_subgraphNFT),
"NFT must be valid"
);
require(_subgraphNFT != address(0), "NFT address cant be zero");
require(Address.isContract(_subgraphNFT), "NFT must be valid");

subgraphNFT = ISubgraphNFT(_subgraphNFT);
emit SubgraphNFTUpdated(_subgraphNFT);
}
Expand Down Expand Up @@ -455,6 +464,36 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
emit SignalBurned(_subgraphID, curator, _nSignal, vSignal, tokens);
}

/**
* @dev 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 override notPartialPaused {
require(_recipient != address(0), "GNS: Curator cannot transfer to the zero address");

// Subgraph checks
SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID);

// Balance checks
address curator = msg.sender;
uint256 curatorBalance = subgraphData.curatorNSignal[curator];
require(curatorBalance >= _amount, "GNS: Curator transfer amount exceeds balance");

// Move the signal
subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].sub(_amount);
subgraphData.curatorNSignal[_recipient] = subgraphData.curatorNSignal[_recipient].add(
_amount
);

emit SignalTransferred(_subgraphID, curator, _recipient, _amount);
}

/**
* @dev Withdraw tokens from a deprecated subgraph.
* When the subgraph is deprecated, any curator can call this function and
Expand Down
6 changes: 6 additions & 0 deletions contracts/discovery/IGNS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ interface IGNS {
uint256 _tokensOutMin
) external;

function transferSignal(
uint256 _subgraphID,
address _recipient,
uint256 _amount
) external;

function withdraw(uint256 _subgraphID) external;

// -- Getters --
Expand Down
73 changes: 71 additions & 2 deletions test/gns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const buildSubgraphID = (account: string, seqID: BigNumber): string =>
describe('GNS', () => {
let me: Account
let other: Account
let another: Account
let governor: Account

let fixture: NetworkFixture
Expand Down Expand Up @@ -444,6 +445,34 @@ describe('GNS', () => {
return tx
}

const transferSignal = async (
subgraphID: string,
owner: Account,
recipient: Account,
amount: BigNumber,
): Promise<ContractTransaction> => {
// Before state
const beforeOwnerNSignal = await gns.getCuratorSignal(subgraphID, owner.address)
const beforeRecipientNSignal = await gns.getCuratorSignal(subgraphID, recipient.address)

// Transfer
const tx = gns.connect(owner.signer).transferSignal(subgraphID, recipient.address, amount)

await expect(tx)
.emit(gns, 'SignalTransferred')
.withArgs(subgraphID, owner.address, recipient.address, amount)

// After state
const afterOwnerNSignal = await gns.getCuratorSignal(subgraphID, owner.address)
const afterRecipientNSignal = await gns.getCuratorSignal(subgraphID, recipient.address)

// Check state
expect(afterOwnerNSignal).eq(beforeOwnerNSignal.sub(amount))
expect(afterRecipientNSignal).eq(beforeRecipientNSignal.add(amount))

return tx
}

const withdraw = async (account: Account, subgraphID: string): Promise<ContractTransaction> => {
// Before state
const beforeCuratorNSignal = await gns.getCuratorSignal(subgraphID, account.address)
Expand Down Expand Up @@ -475,7 +504,7 @@ describe('GNS', () => {
}

before(async function () {
;[me, other, governor] = await getAccounts()
;[me, other, governor, another] = await getAccounts()
fixture = new NetworkFixture()
;({ grt, curation, gns } = await fixture.load(governor.signer))
newSubgraph0 = buildSubgraph()
Expand Down Expand Up @@ -531,7 +560,7 @@ describe('GNS', () => {

it('revert set to empty address', async function () {
const tx = gns.connect(governor.signer).setSubgraphNFT(AddressZero)
await expect(tx).revertedWith('NFT must be valid')
await expect(tx).revertedWith('NFT address cant be zero')
})

it('revert set to non-contract', async function () {
Expand Down Expand Up @@ -824,6 +853,46 @@ describe('GNS', () => {
})
})

describe('transferSignal()', async function () {
let subgraph: Subgraph
let otherNSignal: BigNumber

beforeEach(async () => {
subgraph = await publishNewSubgraph(me, newSubgraph0)
await mintSignal(other, subgraph.id, tokens10000)
otherNSignal = await gns.getCuratorSignal(subgraph.id, other.address)
})

it('should transfer signal from one curator to another', async function () {
await transferSignal(subgraph.id, other, another, otherNSignal)
})
it('should fail when transfering to zero address', async function () {
const tx = gns
.connect(other.signer)
.transferSignal(subgraph.id, ethers.constants.AddressZero, otherNSignal)
await expect(tx).revertedWith('GNS: Curator cannot transfer to the zero address')
})
it('should fail when name signal is disabled', async function () {
await deprecateSubgraph(me, subgraph.id)
const tx = gns
.connect(other.signer)
.transferSignal(subgraph.id, another.address, otherNSignal)
await expect(tx).revertedWith('GNS: Must be active')
})
it('should fail if you try to transfer on a non existing name', async function () {
const subgraphID = randomHexBytes(32)
const tx = gns
.connect(other.signer)
.transferSignal(subgraphID, another.address, otherNSignal)
await expect(tx).revertedWith('GNS: Must be active')
})
it('should fail when the curator tries to transfer more signal than they have', async function () {
const tx = gns
.connect(other.signer)
.transferSignal(subgraph.id, another.address, otherNSignal.add(otherNSignal))
await expect(tx).revertedWith('GNS: Curator transfer amount exceeds balance')
})
})
describe('withdraw()', async function () {
let subgraph: Subgraph

Expand Down