Skip to content

Commit a756b43

Browse files
authored
Merge pull request #527 from graphprotocol/ariel/gns-nft-descriptor
Refactor Subgraph NFT using composition
2 parents 98a471f + cdac3a2 commit a756b43

16 files changed

+526
-131
lines changed
Binary file not shown.

contracts/base/ISubgraphNFTDescriptor.sol

Lines changed: 0 additions & 15 deletions
This file was deleted.

contracts/base/SubgraphNFT.sol

Lines changed: 0 additions & 38 deletions
This file was deleted.

contracts/base/SubgraphNFTDescriptor.sol

Lines changed: 0 additions & 23 deletions
This file was deleted.

contracts/discovery/GNS.sol

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ pragma solidity ^0.7.6;
44
pragma abicoder v2;
55

66
import "@openzeppelin/contracts/math/SafeMath.sol";
7+
import "@openzeppelin/contracts/utils/Address.sol";
78

89
import "../base/Multicall.sol";
9-
import "../base/SubgraphNFT.sol";
1010
import "../bancor/BancorFormula.sol";
1111
import "../upgrades/GraphUpgradeable.sol";
1212
import "../utils/TokenUtils.sol";
@@ -38,6 +38,8 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
3838

3939
// -- Events --
4040

41+
event SubgraphNFTUpdated(address subgraphNFT);
42+
4143
/**
4244
* @dev Emitted when graph account sets its default name
4345
*/
@@ -143,16 +145,16 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
143145
function initialize(
144146
address _controller,
145147
address _bondingCurve,
146-
address _tokenDescriptor
148+
address _subgraphNFT
147149
) external onlyImpl {
148150
Managed._initialize(_controller);
149151

150152
// Dependencies
151153
bondingCurve = _bondingCurve;
152-
__SubgraphNFT_init(_tokenDescriptor);
153154

154155
// Settings
155156
_setOwnerTaxPercentage(500000);
157+
_setSubgraphNFT(_subgraphNFT);
156158
}
157159

158160
/**
@@ -162,6 +164,8 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
162164
graphToken().approve(address(curation()), MAX_UINT256);
163165
}
164166

167+
// -- Config --
168+
165169
/**
166170
* @dev Set the owner fee percentage. This is used to prevent a subgraph owner to drain all
167171
* the name curators tokens while upgrading or deprecating and is configurable in parts per million.
@@ -171,14 +175,6 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
171175
_setOwnerTaxPercentage(_ownerTaxPercentage);
172176
}
173177

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-
182178
/**
183179
* @dev Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all
184180
* the name curators tokens while upgrading or deprecating and is configurable in parts per million.
@@ -190,6 +186,32 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
190186
emit ParameterUpdated("ownerTaxPercentage");
191187
}
192188

189+
/**
190+
* @dev Set the NFT registry contract
191+
* NOTE: Calling this function will break the ownership model unless
192+
* it is replaced with a fully migrated version of the NFT contract state
193+
* Use with care.
194+
* @param _subgraphNFT Address of the ERC721 contract
195+
*/
196+
function setSubgraphNFT(address _subgraphNFT) public onlyGovernor {
197+
_setSubgraphNFT(_subgraphNFT);
198+
}
199+
200+
/**
201+
* @dev Internal: Set the NFT registry contract
202+
* @param _subgraphNFT Address of the ERC721 contract
203+
*/
204+
function _setSubgraphNFT(address _subgraphNFT) private {
205+
require(
206+
_subgraphNFT != address(0) && Address.isContract(_subgraphNFT),
207+
"NFT must be valid"
208+
);
209+
subgraphNFT = ISubgraphNFT(_subgraphNFT);
210+
emit SubgraphNFTUpdated(_subgraphNFT);
211+
}
212+
213+
// -- Actions --
214+
193215
/**
194216
* @dev Allows a graph account to set a default name
195217
* @param _graphAccount Account that is setting its name
@@ -217,7 +239,7 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
217239
override
218240
onlySubgraphAuth(_subgraphID)
219241
{
220-
emit SubgraphMetadataUpdated(_subgraphID, _subgraphMetadata);
242+
_setSubgraphMetadata(_subgraphID, _subgraphMetadata);
221243
}
222244

223245
/**
@@ -241,12 +263,14 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
241263
subgraphData.subgraphDeploymentID = _subgraphDeploymentID;
242264
subgraphData.reserveRatio = defaultReserveRatio;
243265

244-
// Mint the NFT. Use the subgraphID as tokenId.
245-
// This function will check the if tokenId already exists.
246-
_mint(subgraphOwner, subgraphID);
247-
266+
// Mint the NFT. Use the subgraphID as tokenID.
267+
// This function will check the if tokenID already exists.
268+
_mintNFT(subgraphOwner, subgraphID);
248269
emit SubgraphPublished(subgraphID, _subgraphDeploymentID, defaultReserveRatio);
249-
emit SubgraphMetadataUpdated(subgraphID, _subgraphMetadata);
270+
271+
// Set the token metadata
272+
_setSubgraphMetadata(subgraphID, _subgraphMetadata);
273+
250274
emit SubgraphVersionUpdated(subgraphID, _subgraphDeploymentID, _versionMetadata);
251275
}
252276

@@ -356,7 +380,7 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
356380
// subgraphData.subgraphDeploymentID = 0;
357381

358382
// Burn the NFT
359-
_burn(_subgraphID);
383+
_burnNFT(_subgraphID);
360384

361385
emit SubgraphDeprecated(_subgraphID, subgraphData.withdrawableGRT);
362386
}
@@ -637,21 +661,17 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
637661
return 0;
638662
}
639663

640-
/**
641-
* @dev Return the URI describing a particular token ID for a Subgraph.
642-
* @param _subgraphID Subgraph ID
643-
* @return The URI of the ERC721-compliant metadata
644-
*/
645-
function tokenURI(uint256 _subgraphID) public view override returns (string memory) {
646-
return tokenDescriptor.tokenURI(this, _subgraphID);
647-
}
648-
649664
/**
650665
* @dev Create subgraphID for legacy subgraph and mint ownership NFT.
651666
* @param _graphAccount Account that created the subgraph
652667
* @param _subgraphNumber The sequence number of the created subgraph
668+
* @param _subgraphMetadata IPFS hash for the subgraph metadata
653669
*/
654-
function migrateLegacySubgraph(address _graphAccount, uint256 _subgraphNumber) external {
670+
function migrateLegacySubgraph(
671+
address _graphAccount,
672+
uint256 _subgraphNumber,
673+
bytes32 _subgraphMetadata
674+
) external {
655675
// Must be an existing legacy subgraph
656676
bool legacySubgraphExists = legacySubgraphData[_graphAccount][_subgraphNumber]
657677
.subgraphDeploymentID != 0;
@@ -675,9 +695,11 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
675695

676696
// Mint the NFT and send to owner
677697
// The subgraph owner is the graph account that created it
678-
_mint(_graphAccount, subgraphID);
679-
698+
_mintNFT(_graphAccount, subgraphID);
680699
emit LegacySubgraphClaimed(_graphAccount, _subgraphNumber);
700+
701+
// Set the token metadata
702+
_setSubgraphMetadata(subgraphID, _subgraphMetadata);
681703
}
682704

683705
/**
@@ -757,4 +779,45 @@ contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall {
757779
require(_isPublished(subgraphData) == true, "GNS: Must be active");
758780
return subgraphData;
759781
}
782+
783+
// -- NFT --
784+
785+
/**
786+
* @dev Return the owner of a subgraph.
787+
* @param _tokenID Subgraph ID
788+
* @return Owner address
789+
*/
790+
function ownerOf(uint256 _tokenID) public view override returns (address) {
791+
return subgraphNFT.ownerOf(_tokenID);
792+
}
793+
794+
/**
795+
* @dev Mint the NFT for the subgraph.
796+
* @param _owner Owner address
797+
* @param _tokenID Subgraph ID
798+
*/
799+
function _mintNFT(address _owner, uint256 _tokenID) internal {
800+
subgraphNFT.mint(_owner, _tokenID);
801+
}
802+
803+
/**
804+
* @dev Burn the NFT for the subgraph.
805+
* @param _tokenID Subgraph ID
806+
*/
807+
function _burnNFT(uint256 _tokenID) internal {
808+
subgraphNFT.burn(_tokenID);
809+
}
810+
811+
/**
812+
* @dev Set the subgraph metadata.
813+
* @param _tokenID Subgraph ID
814+
* @param _subgraphMetadata IPFS hash of the subgraph metadata
815+
*/
816+
function _setSubgraphMetadata(uint256 _tokenID, bytes32 _subgraphMetadata) internal {
817+
subgraphNFT.setSubgraphMetadata(_tokenID, _subgraphMetadata);
818+
819+
// Even if the following event is emitted in the NFT we emit it here to facilitate
820+
// subgraph indexing
821+
emit SubgraphMetadataUpdated(_tokenID, _subgraphMetadata);
822+
}
760823
}

contracts/discovery/GNSStorage.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
pragma solidity ^0.7.6;
44
pragma abicoder v2;
55

6-
import "../base/SubgraphNFT.sol";
76
import "../governance/Managed.sol";
87

98
import "./erc1056/IEthereumDIDRegistry.sol";
109
import "./IGNS.sol";
10+
import "./ISubgraphNFT.sol";
1111

1212
abstract contract GNSV1Storage is Managed {
1313
// -- State --
@@ -37,12 +37,15 @@ abstract contract GNSV1Storage is Managed {
3737
IEthereumDIDRegistry private __DEPRECATED_erc1056Registry;
3838
}
3939

40-
abstract contract GNSV2Storage is GNSV1Storage, SubgraphNFT {
40+
abstract contract GNSV2Storage is GNSV1Storage {
4141
// Use it whenever a legacy (v1) subgraph NFT was claimed to maintain compatibility
4242
// Keep a reference from subgraphID => (graphAccount, subgraphNumber)
4343
mapping(uint256 => IGNS.LegacySubgraphKey) public legacySubgraphKeys;
4444

4545
// Store data for all NFT-based (v2) subgraphs
4646
// subgraphID => SubgraphData
4747
mapping(uint256 => IGNS.SubgraphData) public subgraphs;
48+
49+
// Contract that represents subgraph ownership through an NFT
50+
ISubgraphNFT public subgraphNFT;
4851
}

contracts/discovery/IGNS.sol

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

2727
function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external;
2828

29-
function setTokenDescriptor(address _tokenDescriptor) external;
30-
3129
// -- Publishing --
3230

3331
function setDefaultName(
@@ -71,6 +69,8 @@ interface IGNS {
7169

7270
// -- Getters --
7371

72+
function ownerOf(uint256 _tokenID) external view returns (address);
73+
7474
function subgraphSignal(uint256 _subgraphID) external view returns (uint256);
7575

7676
function subgraphTokens(uint256 _subgraphID) external view returns (uint256);

contracts/discovery/ISubgraphNFT.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6;
4+
5+
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6+
7+
interface ISubgraphNFT is IERC721 {
8+
// -- Config --
9+
10+
function setMinter(address _minter) external;
11+
12+
function setTokenDescriptor(address _tokenDescriptor) external;
13+
14+
function setBaseURI(string memory _baseURI) external;
15+
16+
// -- Actions --
17+
18+
function mint(address _to, uint256 _tokenId) external;
19+
20+
function burn(uint256 _tokenId) external;
21+
22+
function setSubgraphMetadata(uint256 _tokenId, bytes32 _subgraphMetadata) external;
23+
24+
function tokenURI(uint256 _tokenId) external view returns (string memory);
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.7.6;
4+
5+
/// @title Describes subgraph NFT tokens via URI
6+
interface ISubgraphNFTDescriptor {
7+
/// @notice Produces the URI describing a particular token ID for a Subgraph
8+
/// @dev Note this URI may be data: URI with the JSON contents directly inlined
9+
/// @param _minter Address of the allowed minter
10+
/// @param _tokenId The ID of the subgraph NFT for which to produce a description, which may not be valid
11+
/// @param _baseURI The base URI that could be prefixed to the final URI
12+
/// @param _subgraphMetadata Subgraph metadata set for the subgraph
13+
/// @return The URI of the ERC721-compliant metadata
14+
function tokenURI(
15+
address _minter,
16+
uint256 _tokenId,
17+
string calldata _baseURI,
18+
bytes32 _subgraphMetadata
19+
) external view returns (string memory);
20+
}

0 commit comments

Comments
 (0)