Skip to content

Commit 396d06b

Browse files
committed
feat: add NFT descriptor for a subgraph
1 parent 13a4149 commit 396d06b

File tree

8 files changed

+169
-29
lines changed

8 files changed

+169
-29
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6;
4+
5+
import "../discovery/IGNS.sol";
6+
7+
/// @title Describes subgraph NFT tokens via URI
8+
interface ISubgraphNFTDescriptor {
9+
/// @notice Produces the URI describing a particular token ID for a Subgraph
10+
/// @dev Note this URI may be a data: URI with the JSON contents directly inlined
11+
/// @param _gns GNS contract that holds the Subgraph data
12+
/// @param _subgraphID The ID of the subgraph NFT for which to produce a description, which may not be valid
13+
/// @return The URI of the ERC721-compliant metadata
14+
function tokenURI(IGNS _gns, uint256 _subgraphID) external view returns (string memory);
15+
}

contracts/base/SubgraphNFT.sol

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22

3-
pragma solidity ^0.7.4;
3+
pragma solidity ^0.7.6;
44

55
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
66

7+
import "./ISubgraphNFTDescriptor.sol";
8+
79
abstract contract SubgraphNFT is ERC721Upgradeable {
8-
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {}
10+
ISubgraphNFTDescriptor public tokenDescriptor;
11+
12+
// -- Events --
13+
14+
event TokenDescriptorUpdated(address tokenDescriptor);
15+
16+
// -- Functions --
17+
18+
/**
19+
* @dev Initializes the contract by setting a `name`, `symbol` and `descriptor` to the token collection.
20+
*/
21+
function __SubgraphNFT_init(address _tokenDescriptor) internal initializer {
22+
__ERC721_init("Subgraph", "SG");
23+
_setTokenDescriptor(address(_tokenDescriptor));
24+
}
25+
26+
/**
27+
* @dev Set the token descriptor contract used to create the ERC-721 metadata URI
28+
* @param _tokenDescriptor Address of the contract that creates the NFT token URI
29+
*/
30+
function _setTokenDescriptor(address _tokenDescriptor) internal {
31+
require(
32+
_tokenDescriptor != address(0) && AddressUpgradeable.isContract(_tokenDescriptor),
33+
"NFT: Invalid token descriptor"
34+
);
35+
tokenDescriptor = ISubgraphNFTDescriptor(_tokenDescriptor);
36+
emit TokenDescriptorUpdated(_tokenDescriptor);
37+
}
938
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6;
4+
5+
import "./ISubgraphNFTDescriptor.sol";
6+
7+
/// @title Describes subgraph NFT tokens via URI
8+
contract SubgraphNFTDescriptor is ISubgraphNFTDescriptor {
9+
/// @inheritdoc ISubgraphNFTDescriptor
10+
function tokenURI(IGNS _gns, uint256 _subgraphID)
11+
external
12+
view
13+
override
14+
returns (string memory)
15+
{
16+
// TODO: fancy implementation
17+
// uint256 signal = _gns.subgraphSignal(_subgraphID);
18+
// uint256 tokens = _gns.subgraphTokens(_subgraphID);
19+
// id
20+
// owner
21+
return "";
22+
}
23+
}

contracts/discovery/GNS.sol

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pragma abicoder v2;
66
import "@openzeppelin/contracts/math/SafeMath.sol";
77

88
import "../base/Multicall.sol";
9+
import "../base/SubgraphNFT.sol";
910
import "../bancor/BancorFormula.sol";
1011
import "../upgrades/GraphUpgradeable.sol";
1112
import "../utils/TokenUtils.sol";
@@ -139,12 +140,16 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
139140
/**
140141
* @dev Initialize this contract.
141142
*/
142-
function initialize(address _controller, address _bondingCurve) external onlyImpl {
143+
function initialize(
144+
address _controller,
145+
address _bondingCurve,
146+
address _tokenDescriptor
147+
) external onlyImpl {
143148
Managed._initialize(_controller);
144149

150+
// Dependencies
145151
bondingCurve = _bondingCurve;
146-
// TODO: review token symbol
147-
__ERC721_init("Subgraph", "SUB");
152+
__SubgraphNFT_init(_tokenDescriptor);
148153

149154
// Settings
150155
_setOwnerTaxPercentage(500000);
@@ -166,6 +171,14 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
166171
_setOwnerTaxPercentage(_ownerTaxPercentage);
167172
}
168173

174+
/**
175+
* @dev Set the token descriptor contract.
176+
* @param _tokenDescriptor Address of the contract that creates the NFT token URI
177+
*/
178+
function setTokenDescriptor(address _tokenDescriptor) external override onlyGovernor {
179+
_setTokenDescriptor(_tokenDescriptor);
180+
}
181+
169182
/**
170183
* @dev Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all
171184
* the name curators tokens while upgrading or deprecating and is configurable in parts per hundred.
@@ -598,6 +611,38 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
598611
return _getSubgraphData(_subgraphID).curatorNSignal[_curator];
599612
}
600613

614+
/**
615+
* @dev Return the total signal on the subgraph.
616+
* @param _subgraphID Subgraph ID
617+
* @return Total signal on the subgraph
618+
*/
619+
function subgraphSignal(uint256 _subgraphID) external view override returns (uint256) {
620+
return _getSubgraphData(_subgraphID).nSignal;
621+
}
622+
623+
/**
624+
* @dev Return the total tokens on the subgraph at current value.
625+
* @param _subgraphID Subgraph ID
626+
* @return Total tokens on the subgraph
627+
*/
628+
function subgraphTokens(uint256 _subgraphID) external view override returns (uint256) {
629+
uint256 signal = _getSubgraphData(_subgraphID).nSignal;
630+
if (signal > 0) {
631+
(, uint256 tokens) = nSignalToTokens(_subgraphID, signal);
632+
return tokens;
633+
}
634+
return 0;
635+
}
636+
637+
/**
638+
* @dev Return the URI describing a particular token ID for a Subgraph.
639+
* @param _subgraphID Subgraph ID
640+
* @return The URI of the ERC721-compliant metadata
641+
*/
642+
function tokenURI(uint256 _subgraphID) public view override returns (string memory) {
643+
return tokenDescriptor.tokenURI(this, _subgraphID);
644+
}
645+
601646
/**
602647
* @dev Create subgraphID for legacy subgraph and mint ownership NFT.
603648
* @param _graphAccount Account that created the subgraph

contracts/discovery/GNSStorage.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ abstract contract GNSV1Storage is Managed {
3838
}
3939

4040
abstract contract GNSV2Storage is GNSV1Storage, SubgraphNFT {
41-
// TODO: review order of storage
42-
4341
// Use it whenever a legacy (v1) subgraph NFT was claimed to maintain compatibility
4442
// Keep a reference from subgraphID => (graphAccount, subgraphNumber)
4543
mapping(uint256 => IGNS.LegacySubgraphKey) public legacySubgraphKeys;

contracts/discovery/IGNS.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ interface IGNS {
2626

2727
function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external;
2828

29+
function setTokenDescriptor(address _tokenDescriptor) external;
30+
2931
// -- Publishing --
3032

3133
function setDefaultName(
@@ -69,6 +71,10 @@ interface IGNS {
6971

7072
// -- Getters --
7173

74+
function subgraphSignal(uint256 _subgraphID) external view returns (uint256);
75+
76+
function subgraphTokens(uint256 _subgraphID) external view returns (uint256);
77+
7278
function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn)
7379
external
7480
view

test/gns.test.ts

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { getAccounts, randomHexBytes, Account, toGRT } from './lib/testHelpers'
1010
import { NetworkFixture } from './lib/fixtures'
1111
import { toBN, formatGRT } from './lib/testHelpers'
1212

13+
const { AddressZero } = ethers.constants
14+
1315
// Entities
1416
interface PublishSubgraph {
1517
subgraphDeploymentID: string
@@ -490,6 +492,47 @@ describe('GNS', () => {
490492
await fixture.tearDown()
491493
})
492494

495+
describe('Configuration', async function () {
496+
describe('setOwnerTaxPercentage', function () {
497+
const newValue = 10
498+
499+
it('should set `ownerTaxPercentage`', async function () {
500+
// Can set if allowed
501+
await gns.connect(governor.signer).setOwnerTaxPercentage(newValue)
502+
expect(await gns.ownerTaxPercentage()).eq(newValue)
503+
})
504+
505+
it('reject set `ownerTaxPercentage` if out of bounds', async function () {
506+
const tx = gns.connect(governor.signer).setOwnerTaxPercentage(1000001)
507+
await expect(tx).revertedWith('Owner tax must be MAX_PPM or less')
508+
})
509+
510+
it('reject set `ownerTaxPercentage` if not allowed', async function () {
511+
const tx = gns.connect(me.signer).setOwnerTaxPercentage(newValue)
512+
await expect(tx).revertedWith('Caller must be Controller governor')
513+
})
514+
})
515+
516+
describe('setTokenDescriptor', function () {
517+
it('should set `tokenDescriptor`', async function () {
518+
const newTokenDescriptor = gns.address // I just use any contract address
519+
const tx = gns.connect(governor.signer).setTokenDescriptor(newTokenDescriptor)
520+
await expect(tx).emit(gns, 'TokenDescriptorUpdated').withArgs(newTokenDescriptor)
521+
expect(await gns.tokenDescriptor()).eq(newTokenDescriptor)
522+
})
523+
524+
it('revert set to empty address', async function () {
525+
const tx = gns.connect(governor.signer).setTokenDescriptor(AddressZero)
526+
await expect(tx).revertedWith('NFT: Invalid token descriptor')
527+
})
528+
529+
it('revert set to non-contract', async function () {
530+
const tx = gns.connect(governor.signer).setTokenDescriptor(randomHexBytes(20))
531+
await expect(tx).revertedWith('NFT: Invalid token descriptor')
532+
})
533+
})
534+
})
535+
493536
describe('Publishing names and versions', function () {
494537
describe('setDefaultName', function () {
495538
it('setDefaultName emits the event', async function () {
@@ -865,26 +908,6 @@ describe('GNS', () => {
865908
await mintSignal(me, subgraph.id, tokensToDeposit)
866909
}
867910
})
868-
869-
describe('setOwnerTaxPercentage', function () {
870-
const newValue = 10
871-
872-
it('should set `ownerTaxPercentage`', async function () {
873-
// Can set if allowed
874-
await gns.connect(governor.signer).setOwnerTaxPercentage(newValue)
875-
expect(await gns.ownerTaxPercentage()).eq(newValue)
876-
})
877-
878-
it('reject set `ownerTaxPercentage` if out of bounds', async function () {
879-
const tx = gns.connect(governor.signer).setOwnerTaxPercentage(1000001)
880-
await expect(tx).revertedWith('Owner tax must be MAX_PPM or less')
881-
})
882-
883-
it('reject set `ownerTaxPercentage` if not allowed', async function () {
884-
const tx = gns.connect(me.signer).setOwnerTaxPercentage(newValue)
885-
await expect(tx).revertedWith('Caller must be Controller governor')
886-
})
887-
})
888911
})
889912
})
890913

@@ -938,7 +961,7 @@ describe('GNS', () => {
938961

939962
it('should revert if batching a call to initialize', async function () {
940963
// Call a forbidden function
941-
const tx1 = await gns.populateTransaction.initialize(me.address, me.address)
964+
const tx1 = await gns.populateTransaction.initialize(me.address, me.address, me.address)
942965

943966
// Create a subgraph
944967
const tx2 = await gns.populateTransaction.publishNewSubgraph(

test/lib/deployment.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,13 @@ export async function deployGNS(
175175
): Promise<GNS> {
176176
// Dependency
177177
const bondingCurve = (await deployContract('BancorFormula', deployer)) as unknown as BancorFormula
178+
const subgraphDescriptor = await deployContract('SubgraphNFTDescriptor', deployer)
178179

179180
// Deploy
180181
return network.deployContractWithProxy(
181182
proxyAdmin,
182183
'GNS',
183-
[controller, bondingCurve.address],
184+
[controller, bondingCurve.address, subgraphDescriptor.address],
184185
deployer,
185186
) as unknown as GNS
186187
}

0 commit comments

Comments
 (0)