diff --git a/audits/OpenZeppelin/2021-11-graph-gns-transferrable-owner.pdf b/audits/OpenZeppelin/2021-11-graph-gns-transferrable-owner.pdf new file mode 100644 index 000000000..5bd846e55 Binary files /dev/null and b/audits/OpenZeppelin/2021-11-graph-gns-transferrable-owner.pdf differ diff --git a/cli/commands/migrate.ts b/cli/commands/migrate.ts index 52a64ca8c..b800abde8 100644 --- a/cli/commands/migrate.ts +++ b/cli/commands/migrate.ts @@ -25,6 +25,7 @@ let allContracts = [ 'GraphCurationToken', 'ServiceRegistry', 'Curation', + 'SubgraphNFTDescriptor', 'GNS', 'Staking', 'RewardsManager', diff --git a/contracts/base/ISubgraphNFTDescriptor.sol b/contracts/base/ISubgraphNFTDescriptor.sol new file mode 100644 index 000000000..fc7e2fecc --- /dev/null +++ b/contracts/base/ISubgraphNFTDescriptor.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "../discovery/IGNS.sol"; + +/// @title Describes subgraph NFT tokens via URI +interface ISubgraphNFTDescriptor { + /// @notice Produces the URI describing a particular token ID for a Subgraph + /// @dev Note this URI may be a data: URI with the JSON contents directly inlined + /// @param _gns GNS contract that holds the Subgraph data + /// @param _subgraphID The ID of the subgraph NFT for which to produce a description, which may not be valid + /// @return The URI of the ERC721-compliant metadata + function tokenURI(IGNS _gns, uint256 _subgraphID) external view returns (string memory); +} diff --git a/contracts/base/SubgraphNFT.sol b/contracts/base/SubgraphNFT.sol new file mode 100644 index 000000000..b1e2a94f9 --- /dev/null +++ b/contracts/base/SubgraphNFT.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; + +import "./ISubgraphNFTDescriptor.sol"; + +abstract contract SubgraphNFT is ERC721Upgradeable { + ISubgraphNFTDescriptor public tokenDescriptor; + + // -- Events -- + + event TokenDescriptorUpdated(address tokenDescriptor); + + // -- Functions -- + + /** + * @dev Initializes the contract by setting a `name`, `symbol` and `descriptor` to the token collection. + */ + function __SubgraphNFT_init(address _tokenDescriptor) internal initializer { + __ERC721_init("Subgraph", "SG"); + _setTokenDescriptor(address(_tokenDescriptor)); + } + + /** + * @dev Set the token descriptor contract used to create the ERC-721 metadata URI + * @param _tokenDescriptor Address of the contract that creates the NFT token URI + */ + function _setTokenDescriptor(address _tokenDescriptor) internal { + require( + _tokenDescriptor != address(0) && AddressUpgradeable.isContract(_tokenDescriptor), + "NFT: Invalid token descriptor" + ); + tokenDescriptor = ISubgraphNFTDescriptor(_tokenDescriptor); + emit TokenDescriptorUpdated(_tokenDescriptor); + } +} diff --git a/contracts/base/SubgraphNFTDescriptor.sol b/contracts/base/SubgraphNFTDescriptor.sol new file mode 100644 index 000000000..6809ccc03 --- /dev/null +++ b/contracts/base/SubgraphNFTDescriptor.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.7.6; + +import "./ISubgraphNFTDescriptor.sol"; + +/// @title Describes subgraph NFT tokens via URI +contract SubgraphNFTDescriptor is ISubgraphNFTDescriptor { + /// @inheritdoc ISubgraphNFTDescriptor + function tokenURI(IGNS _gns, uint256 _subgraphID) + external + view + override + returns (string memory) + { + // TODO: fancy implementation + // uint256 signal = _gns.subgraphSignal(_subgraphID); + // uint256 tokens = _gns.subgraphTokens(_subgraphID); + // id + // owner + return ""; + } +} diff --git a/contracts/discovery/GNS.sol b/contracts/discovery/GNS.sol index 1b36f8cfa..12a047210 100644 --- a/contracts/discovery/GNS.sol +++ b/contracts/discovery/GNS.sol @@ -6,6 +6,7 @@ pragma abicoder v2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "../base/Multicall.sol"; +import "../base/SubgraphNFT.sol"; import "../bancor/BancorFormula.sol"; import "../upgrades/GraphUpgradeable.sol"; import "../utils/TokenUtils.sol"; @@ -16,13 +17,13 @@ import "./GNSStorage.sol"; /** * @title GNS * @dev The Graph Name System contract provides a decentralized naming system for subgraphs - * used in the scope of the Graph Network. It translates subgraph names into subgraph versions. + * used in the scope of the Graph Network. It translates Subgraphs into Subgraph Versions. * Each version is associated with a Subgraph Deployment. The contract has no knowledge of * human-readable names. All human readable names emitted in events. * The contract implements a multicall behaviour to support batching multiple calls in a single * transaction. */ -contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { +contract GNS is GNSV2Storage, GraphUpgradeable, IGNS, Multicall { using SafeMath for uint256; // -- Constants -- @@ -48,96 +49,73 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { ); /** - * @dev Emitted when graph account sets a subgraphs metadata on IPFS + * @dev Emitted when the subgraph metadata is updated. */ - event SubgraphMetadataUpdated( - address indexed graphAccount, - uint256 indexed subgraphNumber, - bytes32 subgraphMetadata - ); + event SubgraphMetadataUpdated(uint256 indexed subgraphID, bytes32 subgraphMetadata); /** - * @dev Emitted when a `graph account` publishes a `subgraph` with a `version`. - * Every time this event is emitted, indicates a new version has been created. - * The event also emits a `metadataHash` with subgraph details and version details. + * @dev Emitted when a subgraph version is updated. */ - event SubgraphPublished( - address indexed graphAccount, - uint256 indexed subgraphNumber, + event SubgraphVersionUpdated( + uint256 indexed subgraphID, bytes32 indexed subgraphDeploymentID, bytes32 versionMetadata ); /** - * @dev Emitted when a graph account deprecated one of its subgraphs - */ - event SubgraphDeprecated(address indexed graphAccount, uint256 indexed subgraphNumber); - - /** - * @dev Emitted when a graphAccount creates an nSignal bonding curve that - * points to a subgraph deployment + * @dev Emitted when a curator mints signal. */ - event NameSignalEnabled( - address indexed graphAccount, - uint256 indexed subgraphNumber, - bytes32 indexed subgraphDeploymentID, - uint32 reserveRatio - ); - - /** - * @dev Emitted when a name curator deposits its vSignal into an nSignal curve to mint nSignal - */ - event NSignalMinted( - address indexed graphAccount, - uint256 indexed subgraphNumber, - address indexed nameCurator, + event SignalMinted( + uint256 indexed subgraphID, + address indexed curator, uint256 nSignalCreated, uint256 vSignalCreated, uint256 tokensDeposited ); /** - * @dev Emitted when a name curator burns its nSignal, which in turn burns - * the vSignal, and receives GRT + * @dev Emitted when a curator burns signal. */ - event NSignalBurned( - address indexed graphAccount, - uint256 indexed subgraphNumber, - address indexed nameCurator, + event SignalBurned( + uint256 indexed subgraphID, + address indexed curator, uint256 nSignalBurnt, uint256 vSignalBurnt, uint256 tokensReceived ); /** - * @dev Emitted when a graph account upgrades its nSignal curve to point to a new + * @dev Emitted when a subgraph is created. + */ + event SubgraphPublished( + uint256 indexed subgraphID, + bytes32 indexed subgraphDeploymentID, + uint32 reserveRatio + ); + + /** + * @dev Emitted when a subgraph is upgraded to point to a new * subgraph deployment, burning all the old vSignal and depositing the GRT into the - * new vSignal curve, creating new nSignal + * new vSignal curve. */ - event NameSignalUpgrade( - address indexed graphAccount, - uint256 indexed subgraphNumber, - uint256 newVSignalCreated, + event SubgraphUpgraded( + uint256 indexed subgraphID, + uint256 vSignalCreated, uint256 tokensSignalled, bytes32 indexed subgraphDeploymentID ); /** - * @dev Emitted when an nSignal curve has been permanently disabled + * @dev Emitted when a subgraph is deprecated. */ - event NameSignalDisabled( - address indexed graphAccount, - uint256 indexed subgraphNumber, - uint256 withdrawableGRT - ); + event SubgraphDeprecated(uint256 indexed subgraphID, uint256 withdrawableGRT); /** - * @dev Emitted when a nameCurator withdraws its GRT from a deprecated name signal pool + * @dev Emitted when a curator withdraws GRT from a deprecated subgraph */ event GRTWithdrawn( - address indexed graphAccount, - uint256 indexed subgraphNumber, - address indexed nameCurator, + uint256 indexed subgraphID, + address indexed curator, uint256 nSignalBurnt, uint256 withdrawnGRT ); @@ -145,20 +123,15 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { // -- Modifiers -- /** - * @dev Check if the owner is the graph account - * @param _graphAccount Address of the graph account + * @dev Emitted when a legacy subgraph is claimed */ - function _isGraphAccountOwner(address _graphAccount) private view { - address graphAccountOwner = erc1056Registry.identityOwner(_graphAccount); - require(graphAccountOwner == msg.sender, "GNS: Only graph account owner can call"); - } + event LegacySubgraphClaimed(address indexed graphAccount, uint256 subgraphNumber); /** - * @dev Modifier that allows a function to be called by owner of a graph account - * @param _graphAccount Address of the graph account + * @dev Modifier that allows only a subgraph operator to be the caller */ - modifier onlyGraphAccountOwner(address _graphAccount) { - _isGraphAccountOwner(_graphAccount); + modifier onlySubgraphAuth(uint256 _subgraphID) { + require(ownerOf(_subgraphID) == msg.sender, "GNS: Must be authorized"); _; } @@ -170,12 +143,13 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { function initialize( address _controller, address _bondingCurve, - address _didRegistry + address _tokenDescriptor ) external onlyImpl { Managed._initialize(_controller); + // Dependencies bondingCurve = _bondingCurve; - erc1056Registry = IEthereumDIDRegistry(_didRegistry); + __SubgraphNFT_init(_tokenDescriptor); // Settings _setOwnerTaxPercentage(500000); @@ -190,16 +164,24 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { /** * @dev Set the owner fee percentage. This is used to prevent a subgraph owner to drain all - * the name curators tokens while upgrading or deprecating and is configurable in parts per hundred. + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. * @param _ownerTaxPercentage Owner tax percentage */ function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external override onlyGovernor { _setOwnerTaxPercentage(_ownerTaxPercentage); } + /** + * @dev Set the token descriptor contract. + * @param _tokenDescriptor Address of the contract that creates the NFT token URI + */ + function setTokenDescriptor(address _tokenDescriptor) external override onlyGovernor { + _setTokenDescriptor(_tokenDescriptor); + } + /** * @dev Internal: Set the owner tax percentage. This is used to prevent a subgraph owner to drain all - * the name curators tokens while upgrading or deprecating and is configurable in parts per hundred. + * the name curators tokens while upgrading or deprecating and is configurable in parts per million. * @param _ownerTaxPercentage Owner tax percentage */ function _setOwnerTaxPercentage(uint32 _ownerTaxPercentage) private { @@ -220,325 +202,264 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { uint8 _nameSystem, bytes32 _nameIdentifier, string calldata _name - ) external override onlyGraphAccountOwner(_graphAccount) { + ) external override { + require(_graphAccount == msg.sender, "GNS: Only you can set your name"); emit SetDefaultName(_graphAccount, _nameSystem, _nameIdentifier, _name); } /** - * @dev Allows a graph account update the metadata of a subgraph they have published - * @param _graphAccount Account that owns the subgraph - * @param _subgraphNumber Subgraph number + * @dev Allows a subgraph owner to update the metadata of a subgraph they have published + * @param _subgraphID Subgraph ID * @param _subgraphMetadata IPFS hash for the subgraph metadata */ - function updateSubgraphMetadata( - address _graphAccount, - uint256 _subgraphNumber, - bytes32 _subgraphMetadata - ) public override onlyGraphAccountOwner(_graphAccount) { - emit SubgraphMetadataUpdated(_graphAccount, _subgraphNumber, _subgraphMetadata); + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) + public + override + onlySubgraphAuth(_subgraphID) + { + emit SubgraphMetadataUpdated(_subgraphID, _subgraphMetadata); } /** - * @dev Allows a graph account to publish a new subgraph, which means a new subgraph number - * will be used. - * @param _graphAccount Account that is publishing the subgraph - * @param _subgraphDeploymentID Subgraph deployment ID of the version, linked to the name + * @dev Publish a new subgraph. + * @param _subgraphDeploymentID Subgraph deployment for the subgraph * @param _versionMetadata IPFS hash for the subgraph version metadata * @param _subgraphMetadata IPFS hash for the subgraph metadata */ function publishNewSubgraph( - address _graphAccount, bytes32 _subgraphDeploymentID, bytes32 _versionMetadata, bytes32 _subgraphMetadata - ) external override notPaused onlyGraphAccountOwner(_graphAccount) { - uint256 subgraphNumber = graphAccountSubgraphNumbers[_graphAccount]; - _publishVersion(_graphAccount, subgraphNumber, _subgraphDeploymentID, _versionMetadata); - graphAccountSubgraphNumbers[_graphAccount] = graphAccountSubgraphNumbers[_graphAccount].add( - 1 - ); - updateSubgraphMetadata(_graphAccount, subgraphNumber, _subgraphMetadata); - _enableNameSignal(_graphAccount, subgraphNumber); + ) external override notPaused { + // Subgraph deployment must be non-empty + require(_subgraphDeploymentID != 0, "GNS: Cannot set deploymentID to 0 in publish"); + + // Init the subgraph + address subgraphOwner = msg.sender; + uint256 subgraphID = _nextSubgraphID(subgraphOwner); + SubgraphData storage subgraphData = _getSubgraphData(subgraphID); + subgraphData.subgraphDeploymentID = _subgraphDeploymentID; + subgraphData.reserveRatio = defaultReserveRatio; + + // Mint the NFT. Use the subgraphID as tokenId. + // This function will check the if tokenId already exists. + _mint(subgraphOwner, subgraphID); + + emit SubgraphPublished(subgraphID, _subgraphDeploymentID, defaultReserveRatio); + emit SubgraphMetadataUpdated(subgraphID, _subgraphMetadata); + emit SubgraphVersionUpdated(subgraphID, _subgraphDeploymentID, _versionMetadata); } /** - * @dev Allows a graph account to publish a new version of its subgraph. - * Version is derived from the occurrence of SubgraphPublished being emitted. - * The first time SubgraphPublished is called would be Version 0 - * @param _graphAccount Account that is publishing the subgraph - * @param _subgraphNumber Subgraph number for the account - * @param _subgraphDeploymentID Subgraph deployment ID of the version, linked to the name + * @dev Publish a new version of an existing subgraph. + * @param _subgraphID Subgraph ID + * @param _subgraphDeploymentID Subgraph deployment ID of the new version * @param _versionMetadata IPFS hash for the subgraph version metadata */ function publishNewVersion( - address _graphAccount, - uint256 _subgraphNumber, + uint256 _subgraphID, bytes32 _subgraphDeploymentID, bytes32 _versionMetadata - ) external override notPaused onlyGraphAccountOwner(_graphAccount) { - require( - isPublished(_graphAccount, _subgraphNumber), - "GNS: Cannot update version if not published, or has been deprecated" - ); - bytes32 oldSubgraphDeploymentID = subgraphs[_graphAccount][_subgraphNumber]; - require( - _subgraphDeploymentID != oldSubgraphDeploymentID, - "GNS: Cannot publish a new version with the same subgraph deployment ID" - ); + ) external override notPaused onlySubgraphAuth(_subgraphID) { + // Perform the upgrade from the current subgraph deployment to the new one. + // This involves burning all signal from the old deployment and using the funds to buy + // from the new deployment. + // This will also make the change to target to the new deployment. - _publishVersion(_graphAccount, _subgraphNumber, _subgraphDeploymentID, _versionMetadata); - _upgradeNameSignal(_graphAccount, _subgraphNumber, _subgraphDeploymentID); - } + // Subgraph check + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); - /** - * @dev Private function used by both external publishing functions - * @param _graphAccount Account that is publishing the subgraph - * @param _subgraphNumber Subgraph number for the account - * @param _subgraphDeploymentID Subgraph deployment ID of the version, linked to the name - * @param _versionMetadata IPFS hash for the subgraph version metadata - */ - function _publishVersion( - address _graphAccount, - uint256 _subgraphNumber, - bytes32 _subgraphDeploymentID, - bytes32 _versionMetadata - ) private { + // New subgraph deployment must be non-empty require(_subgraphDeploymentID != 0, "GNS: Cannot set deploymentID to 0 in publish"); - // Stores a subgraph deployment ID, which indicates a version has been created - subgraphs[_graphAccount][_subgraphNumber] = _subgraphDeploymentID; - - // Emit version and name data - emit SubgraphPublished( - _graphAccount, - _subgraphNumber, - _subgraphDeploymentID, - _versionMetadata + // New subgraph deployment must be different than current + require( + _subgraphDeploymentID != subgraphData.subgraphDeploymentID, + "GNS: Cannot publish a new version with the same subgraph deployment ID" ); - } - /** - * @dev Deprecate a subgraph. Can only be done by the graph account owner. - * @param _graphAccount Account that is deprecating the subgraph - * @param _subgraphNumber Subgraph number for the account - */ - function deprecateSubgraph(address _graphAccount, uint256 _subgraphNumber) - external - override - notPaused - onlyGraphAccountOwner(_graphAccount) - { + // This is to prevent the owner from front running its name curators signal by posting + // its own signal ahead, bringing the name curators in, and dumping on them + ICuration curation = curation(); require( - isPublished(_graphAccount, _subgraphNumber), - "GNS: Cannot deprecate a subgraph which does not exist" + !curation.isCurated(_subgraphDeploymentID), + "GNS: Owner cannot point to a subgraphID that has been pre-curated" ); - delete subgraphs[_graphAccount][_subgraphNumber]; - emit SubgraphDeprecated(_graphAccount, _subgraphNumber); + // 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) + // Sell all signal from the old deployment + uint256 tokens = curation.burn( + subgraphData.subgraphDeploymentID, + subgraphData.vSignal, + 0 + ); + + // Take the owner cut of the curation tax, add it to the total + // Upgrade is only callable by the owner, we assume then that msg.sender = owner + address subgraphOwner = msg.sender; + uint256 tokensWithTax = _chargeOwnerTax( + tokens, + subgraphOwner, + curation.curationTaxPercentage() + ); - _disableNameSignal(_graphAccount, _subgraphNumber); - } + // Update pool: constant nSignal, vSignal can change (w/no slippage protection) + // Buy all signal from the new deployment + (subgraphData.vSignal, ) = curation.mint(_subgraphDeploymentID, tokensWithTax, 0); - /** - * @dev Enable name signal on a graph accounts numbered subgraph, which points to a subgraph - * deployment - * @param _graphAccount Graph account enabling name signal - * @param _subgraphNumber Subgraph number being used - */ - function _enableNameSignal(address _graphAccount, uint256 _subgraphNumber) private { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - namePool.subgraphDeploymentID = subgraphs[_graphAccount][_subgraphNumber]; - namePool.reserveRatio = defaultReserveRatio; + emit SubgraphUpgraded( + _subgraphID, + subgraphData.vSignal, + tokensWithTax, + _subgraphDeploymentID + ); + } - emit NameSignalEnabled( - _graphAccount, - _subgraphNumber, - namePool.subgraphDeploymentID, - namePool.reserveRatio - ); + // Update target deployment + subgraphData.subgraphDeploymentID = _subgraphDeploymentID; + + emit SubgraphVersionUpdated(_subgraphID, _subgraphDeploymentID, _versionMetadata); } /** - * @dev Update a name signal on a graph accounts numbered subgraph - * @param _graphAccount Graph account updating name signal - * @param _subgraphNumber Subgraph number being used - * @param _newSubgraphDeploymentID Deployment ID being upgraded to + * @dev Deprecate a subgraph. The bonding curve is destroyed, the vSignal is burned, and the GNS + * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. + * Can only be done by the subgraph owner. + * @param _subgraphID Subgraph ID */ - function _upgradeNameSignal( - address _graphAccount, - uint256 _subgraphNumber, - bytes32 _newSubgraphDeploymentID - ) private { - // This is to prevent the owner from front running its name curators signal by posting - // its own signal ahead, bringing the name curators in, and dumping on them - ICuration curation = curation(); - require( - !curation.isCurated(_newSubgraphDeploymentID), - "GNS: Owner cannot point to a subgraphID that has been pre-curated" - ); - - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - require( - namePool.nSignal > 0, - "GNS: There must be nSignal on this subgraph for curve math to work" - ); - require(namePool.disabled == false, "GNS: Cannot be disabled"); - - // Burn all version signal in the name pool for tokens - uint256 tokens = curation.burn(namePool.subgraphDeploymentID, namePool.vSignal, 0); + function deprecateSubgraph(uint256 _subgraphID) + external + override + notPaused + onlySubgraphAuth(_subgraphID) + { + // Subgraph check + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); + + // Burn signal only if it has any available + if (subgraphData.nSignal > 0) { + subgraphData.withdrawableGRT = curation().burn( + subgraphData.subgraphDeploymentID, + subgraphData.vSignal, + 0 + ); + } - // Take the owner cut of the curation tax, add it to the total - uint32 curationTaxPercentage = curation.curationTaxPercentage(); - uint256 tokensWithTax = _chargeOwnerTax(tokens, _graphAccount, curationTaxPercentage); + // Deprecate the subgraph and do cleanup + subgraphData.disabled = true; + subgraphData.vSignal = 0; + subgraphData.reserveRatio = 0; + // NOTE: We don't reset the following variable as we use it to test if the Subgraph was ever created + // subgraphData.subgraphDeploymentID = 0; - // Update pool: constant nSignal, vSignal can change - namePool.subgraphDeploymentID = _newSubgraphDeploymentID; - (namePool.vSignal, ) = curation.mint(namePool.subgraphDeploymentID, tokensWithTax, 0); + // Burn the NFT + _burn(_subgraphID); - emit NameSignalUpgrade( - _graphAccount, - _subgraphNumber, - namePool.vSignal, - tokensWithTax, - _newSubgraphDeploymentID - ); + emit SubgraphDeprecated(_subgraphID, subgraphData.withdrawableGRT); } /** - * @dev Allow a name curator to mint some nSignal by depositing GRT - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number + * @dev Deposit GRT into a subgraph and mint signal. + * @param _subgraphID Subgraph ID * @param _tokensIn The amount of tokens the nameCurator wants to deposit * @param _nSignalOutMin Expected minimum amount of name signal to receive */ - function mintNSignal( - address _graphAccount, - uint256 _subgraphNumber, + function mintSignal( + uint256 _subgraphID, uint256 _tokensIn, uint256 _nSignalOutMin ) external override notPartialPaused { - // Pool checks - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - require(namePool.disabled == false, "GNS: Cannot be disabled"); - require( - namePool.subgraphDeploymentID != 0, - "GNS: Must deposit on a name signal that exists" - ); + // Subgraph checks + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); // Pull tokens from sender - TokenUtils.pullTokens(graphToken(), msg.sender, _tokensIn); + address curator = msg.sender; + TokenUtils.pullTokens(graphToken(), curator, _tokensIn); // Get name signal to mint for tokens deposited - (uint256 vSignal, ) = curation().mint(namePool.subgraphDeploymentID, _tokensIn, 0); - uint256 nSignal = vSignalToNSignal(_graphAccount, _subgraphNumber, vSignal); + (uint256 vSignal, ) = curation().mint(subgraphData.subgraphDeploymentID, _tokensIn, 0); + uint256 nSignal = vSignalToNSignal(_subgraphID, vSignal); // Slippage protection require(nSignal >= _nSignalOutMin, "GNS: Slippage protection"); // Update pools - namePool.vSignal = namePool.vSignal.add(vSignal); - namePool.nSignal = namePool.nSignal.add(nSignal); - namePool.curatorNSignal[msg.sender] = namePool.curatorNSignal[msg.sender].add(nSignal); + subgraphData.vSignal = subgraphData.vSignal.add(vSignal); + subgraphData.nSignal = subgraphData.nSignal.add(nSignal); + subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].add(nSignal); - emit NSignalMinted(_graphAccount, _subgraphNumber, msg.sender, nSignal, vSignal, _tokensIn); + emit SignalMinted(_subgraphID, curator, nSignal, vSignal, _tokensIn); } /** - * @dev Allow a nameCurator to burn some of its nSignal and get GRT in return - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators + * @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 burnNSignal( - address _graphAccount, - uint256 _subgraphNumber, + function burnSignal( + uint256 _subgraphID, uint256 _nSignal, uint256 _tokensOutMin ) external override notPartialPaused { - // Pool checks - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - require(namePool.disabled == false, "GNS: Cannot be disabled"); + // Subgraph checks + SubgraphData storage subgraphData = _getSubgraphOrRevert(_subgraphID); // Curator balance checks - uint256 curatorNSignal = namePool.curatorNSignal[msg.sender]; + address curator = msg.sender; + uint256 curatorNSignal = subgraphData.curatorNSignal[curator]; require( _nSignal <= curatorNSignal, "GNS: Curator cannot withdraw more nSignal than they have" ); // Get tokens for name signal amount to burn - uint256 vSignal = nSignalToVSignal(_graphAccount, _subgraphNumber, _nSignal); - uint256 tokens = curation().burn(namePool.subgraphDeploymentID, vSignal, _tokensOutMin); + uint256 vSignal = nSignalToVSignal(_subgraphID, _nSignal); + uint256 tokens = curation().burn(subgraphData.subgraphDeploymentID, vSignal, _tokensOutMin); // Update pools - namePool.vSignal = namePool.vSignal.sub(vSignal); - namePool.nSignal = namePool.nSignal.sub(_nSignal); - namePool.curatorNSignal[msg.sender] = namePool.curatorNSignal[msg.sender].sub(_nSignal); - - // Return the tokens to the curator - TokenUtils.pushTokens(graphToken(), msg.sender, tokens); - - emit NSignalBurned(_graphAccount, _subgraphNumber, msg.sender, _nSignal, vSignal, tokens); - } + subgraphData.vSignal = subgraphData.vSignal.sub(vSignal); + subgraphData.nSignal = subgraphData.nSignal.sub(_nSignal); + subgraphData.curatorNSignal[curator] = subgraphData.curatorNSignal[curator].sub(_nSignal); - /** - * @dev Owner disables the subgraph. This means the subgraph-number combination can no longer - * be used for name signal. The nSignal curve is destroyed, the vSignal is burned, and the GNS - * contract holds the GRT from burning the vSignal, which all curators can withdraw manually. - * @param _graphAccount Account that is deprecating its name curation - * @param _subgraphNumber Subgraph number - */ - function _disableNameSignal(address _graphAccount, uint256 _subgraphNumber) private { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - - // If no nSignal, then no need to burn vSignal - if (namePool.nSignal != 0) { - // Note: No slippage, burn at any cost - namePool.withdrawableGRT = curation().burn( - namePool.subgraphDeploymentID, - namePool.vSignal, - 0 - ); - namePool.vSignal = 0; - } - - // Set the NameCurationPool fields to make it disabled - namePool.disabled = true; + // Return the tokens to the nameCurator + require(graphToken().transfer(curator, tokens), "GNS: Error sending tokens"); - emit NameSignalDisabled(_graphAccount, _subgraphNumber, namePool.withdrawableGRT); + emit SignalBurned(_subgraphID, curator, _nSignal, vSignal, tokens); } /** - * @dev When the subgraph curve is disabled, all nameCurators can call this function and - * withdraw the GRT they are entitled for its original deposit of vSignal - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators + * @dev Withdraw tokens from a deprecated subgraph. + * When the subgraph is deprecated, any curator can call this function and + * withdraw the GRT they are entitled for its original deposit + * @param _subgraphID Subgraph ID */ - function withdraw(address _graphAccount, uint256 _subgraphNumber) - external - override - notPartialPaused - { - // Pool checks - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - require(namePool.disabled == true, "GNS: Name bonding curve must be disabled first"); - require(namePool.withdrawableGRT > 0, "GNS: No more GRT to withdraw"); + function withdraw(uint256 _subgraphID) external override notPartialPaused { + // Subgraph validations + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + require(subgraphData.disabled == true, "GNS: Must be disabled first"); + require(subgraphData.withdrawableGRT > 0, "GNS: No more GRT to withdraw"); - // Curator balance checks - uint256 curatorNSignal = namePool.curatorNSignal[msg.sender]; - require(curatorNSignal > 0, "GNS: Curator must have some nSignal to withdraw GRT"); + // Curator validations + address curator = msg.sender; + uint256 curatorNSignal = subgraphData.curatorNSignal[curator]; + require(curatorNSignal > 0, "GNS: No signal to withdraw GRT"); // Get curator share of tokens to be withdrawn - uint256 tokensOut = curatorNSignal.mul(namePool.withdrawableGRT).div(namePool.nSignal); - namePool.curatorNSignal[msg.sender] = 0; - namePool.nSignal = namePool.nSignal.sub(curatorNSignal); - namePool.withdrawableGRT = namePool.withdrawableGRT.sub(tokensOut); + uint256 tokensOut = curatorNSignal.mul(subgraphData.withdrawableGRT).div( + subgraphData.nSignal + ); + subgraphData.curatorNSignal[curator] = 0; + subgraphData.nSignal = subgraphData.nSignal.sub(curatorNSignal); + subgraphData.withdrawableGRT = subgraphData.withdrawableGRT.sub(tokensOut); // Return tokens to the curator - TokenUtils.pushTokens(graphToken(), msg.sender, tokensOut); + TokenUtils.pushTokens(graphToken(), curator, tokensOut); - emit GRTWithdrawn(_graphAccount, _subgraphNumber, msg.sender, curatorNSignal, tokensOut); + emit GRTWithdrawn(_subgraphID, curator, curatorNSignal, tokensOut); } /** @@ -584,17 +505,12 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { } /** - * @dev Calculate name signal to be returned for an amount of tokens. - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators - * @param _tokensIn Tokens being exchanged for name signal - * @return Amount of name signal and curation tax + * @dev Calculate subgraph signal to be returned for an amount of tokens. + * @param _subgraphID Subgraph ID + * @param _tokensIn Tokens being exchanged for subgraph signal + * @return Amount of subgraph signal and curation tax */ - function tokensToNSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _tokensIn - ) + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) public view override @@ -604,110 +520,241 @@ contract GNS is GNSV1Storage, GraphUpgradeable, IGNS, Multicall { uint256 ) { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); (uint256 vSignal, uint256 curationTax) = curation().tokensToSignal( - namePool.subgraphDeploymentID, + subgraphData.subgraphDeploymentID, _tokensIn ); - uint256 nSignal = vSignalToNSignal(_graphAccount, _subgraphNumber, vSignal); + uint256 nSignal = vSignalToNSignal(_subgraphID, vSignal); return (vSignal, nSignal, curationTax); } /** - * @dev Calculate tokens returned for an amount of name signal. - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators - * @param _nSignalIn Name signal being exchanged for tokens - * @return Amount of tokens returned for an amount of nSignal + * @dev Calculate tokens returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for tokens + * @return Amount of tokens returned for an amount of subgraph signal */ - function nSignalToTokens( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _nSignalIn - ) public view override returns (uint256, uint256) { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; - uint256 vSignal = nSignalToVSignal(_graphAccount, _subgraphNumber, _nSignalIn); - uint256 tokensOut = curation().signalToTokens(namePool.subgraphDeploymentID, vSignal); + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + public + view + override + returns (uint256, uint256) + { + // Get subgraph or revert if not published + // 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); return (vSignal, tokensOut); } /** - * @dev Calculate nSignal to be returned for an amount of vSignal. - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators - * @param _vSignalIn Amount of vSignal to exchange for name signal - * @return Amount of nSignal that can be bought + * @dev Calculate subgraph signal to be returned for an amount of subgraph deployment signal. + * @param _subgraphID Subgraph ID + * @param _vSignalIn Amount of subgraph deployment signal to exchange for subgraph signal + * @return Amount of subgraph signal that can be bought */ - function vSignalToNSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _vSignalIn - ) public view override returns (uint256) { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + public + view + override + returns (uint256) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); // Handle initialization by using 1:1 version to name signal - if (namePool.vSignal == 0) { + if (subgraphData.vSignal == 0) { return _vSignalIn; } return BancorFormula(bondingCurve).calculatePurchaseReturn( - namePool.nSignal, - namePool.vSignal, - namePool.reserveRatio, + subgraphData.nSignal, + subgraphData.vSignal, + subgraphData.reserveRatio, _vSignalIn ); } /** - * @dev Calculate vSignal to be returned for an amount of name signal. - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators - * @param _nSignalIn Name signal being exchanged for vSignal - * @return Amount of vSignal that can be returned + * @dev Calculate subgraph deployment signal to be returned for an amount of subgraph signal. + * @param _subgraphID Subgraph ID + * @param _nSignalIn Subgraph signal being exchanged for subgraph deployment signal + * @return Amount of subgraph deployment signal that can be returned */ - function nSignalToVSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _nSignalIn - ) public view override returns (uint256) { - NameCurationPool storage namePool = nameSignals[_graphAccount][_subgraphNumber]; + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + public + view + override + returns (uint256) + { + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); return BancorFormula(bondingCurve).calculateSaleReturn( - namePool.nSignal, - namePool.vSignal, - namePool.reserveRatio, + subgraphData.nSignal, + subgraphData.vSignal, + subgraphData.reserveRatio, _nSignalIn ); } /** - * @dev Get the amount of name signal a curator has on a name pool. - * @param _graphAccount Subgraph owner - * @param _subgraphNumber Subgraph owners subgraph number which was curated on by nameCurators - * @param _curator Curator to look up to see n signal balance - * @return Amount of name signal owned by a curator for the name pool + * @dev Get the amount of subgraph signal a curator has. + * @param _subgraphID Subgraph ID + * @param _curator Curator address + * @return Amount of subgraph signal owned by a curator */ - function getCuratorNSignal( - address _graphAccount, - uint256 _subgraphNumber, - address _curator - ) public view override returns (uint256) { - return nameSignals[_graphAccount][_subgraphNumber].curatorNSignal[_curator]; + function getCuratorSignal(uint256 _subgraphID, address _curator) + public + view + override + returns (uint256) + { + return _getSubgraphData(_subgraphID).curatorNSignal[_curator]; + } + + /** + * @dev Return the total signal on the subgraph. + * @param _subgraphID Subgraph ID + * @return Total signal on the subgraph + */ + function subgraphSignal(uint256 _subgraphID) external view override returns (uint256) { + return _getSubgraphData(_subgraphID).nSignal; } /** - * @dev Return whether a subgraph name is published. - * @param _graphAccount Account being checked - * @param _subgraphNumber Subgraph number being checked for publishing + * @dev Return the total tokens on the subgraph at current value. + * @param _subgraphID Subgraph ID + * @return Total tokens on the subgraph + */ + function subgraphTokens(uint256 _subgraphID) external view override returns (uint256) { + uint256 signal = _getSubgraphData(_subgraphID).nSignal; + if (signal > 0) { + (, uint256 tokens) = nSignalToTokens(_subgraphID, signal); + return tokens; + } + return 0; + } + + /** + * @dev Return the URI describing a particular token ID for a Subgraph. + * @param _subgraphID Subgraph ID + * @return The URI of the ERC721-compliant metadata + */ + function tokenURI(uint256 _subgraphID) public view override returns (string memory) { + return tokenDescriptor.tokenURI(this, _subgraphID); + } + + /** + * @dev Create subgraphID for legacy subgraph and mint ownership NFT. + * @param _graphAccount Account that created the subgraph + * @param _subgraphNumber The sequence number of the created subgraph + */ + function migrateLegacySubgraph(address _graphAccount, uint256 _subgraphNumber) external { + // Must be an existing legacy subgraph + bool legacySubgraphExists = legacySubgraphData[_graphAccount][_subgraphNumber] + .subgraphDeploymentID != 0; + require(legacySubgraphExists == true, "GNS: Subgraph does not exist"); + + // Must not be a claimed subgraph + uint256 subgraphID = _buildSubgraphID(_graphAccount, _subgraphNumber); + require( + legacySubgraphKeys[subgraphID].account == address(0), + "GNS: Subgraph was already claimed" + ); + + // Store a reference for a legacy subgraph + legacySubgraphKeys[subgraphID] = IGNS.LegacySubgraphKey({ + account: _graphAccount, + accountSeqID: _subgraphNumber + }); + + // Delete state for legacy subgraph + legacySubgraphs[_graphAccount][_subgraphNumber] = 0; + + // Mint the NFT and send to owner + // The subgraph owner is the graph account that created it + _mint(_graphAccount, subgraphID); + + emit LegacySubgraphClaimed(_graphAccount, _subgraphNumber); + } + + /** + * @dev Return whether a subgraph is published. + * @param _subgraphID Subgraph ID * @return Return true if subgraph is currently published */ - function isPublished(address _graphAccount, uint256 _subgraphNumber) - public + function isPublished(uint256 _subgraphID) public view override returns (bool) { + return _isPublished(_getSubgraphData(_subgraphID)); + } + + /** + * @dev Build a subgraph ID based on the account creating it and a sequence number for that account. + * Subgraph ID is the keccak hash of account+seqID + * @return Subgraph ID + */ + function _buildSubgraphID(address _account, uint256 _seqID) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(_account, _seqID))); + } + + /** + * @dev Return the next subgraphID given the account that is creating the subgraph. + * NOTE: This function updates the sequence ID for the account + * @return Sequence ID for the account + */ + function _nextSubgraphID(address _account) internal returns (uint256) { + return _buildSubgraphID(_account, _nextAccountSeqID(_account)); + } + + /** + * @dev Return a new consecutive sequence ID for an account and update to the next value. + * NOTE: This function updates the sequence ID for the account + * @return Sequence ID for the account + */ + function _nextAccountSeqID(address _account) internal returns (uint256) { + uint256 seqID = nextAccountSeqID[_account]; + nextAccountSeqID[_account] = nextAccountSeqID[_account].add(1); + return seqID; + } + + /** + * @dev Get subgraph data. + * This function will first look for a v1 subgraph and return it if found. + * @param _subgraphID Subgraph ID + * @return Subgraph Data + */ + function _getSubgraphData(uint256 _subgraphID) private view returns (SubgraphData storage) { + // If there is a legacy subgraph created return it + LegacySubgraphKey storage legacySubgraphKey = legacySubgraphKeys[_subgraphID]; + if (legacySubgraphKey.account != address(0)) { + return legacySubgraphData[legacySubgraphKey.account][legacySubgraphKey.accountSeqID]; + } + // Return new subgraph type + return subgraphs[_subgraphID]; + } + + /** + * @dev Return whether a subgraph is published. + * @param _subgraphData Subgraph Data + * @return Return true if subgraph is currently published + */ + function _isPublished(SubgraphData storage _subgraphData) internal view returns (bool) { + return _subgraphData.subgraphDeploymentID != 0 && _subgraphData.disabled == false; + } + + /** + * @dev Return the subgraph data or revert if not published or deprecated. + * @param _subgraphID Subgraph ID + * @return Subgraph Data + */ + function _getSubgraphOrRevert(uint256 _subgraphID) + internal view - override - returns (bool) + returns (SubgraphData storage) { - return subgraphs[_graphAccount][_subgraphNumber] != 0; + SubgraphData storage subgraphData = _getSubgraphData(_subgraphID); + require(_isPublished(subgraphData) == true, "GNS: Must be active"); + return subgraphData; } } diff --git a/contracts/discovery/GNSStorage.sol b/contracts/discovery/GNSStorage.sol index 96afbb4f4..ee5973719 100644 --- a/contracts/discovery/GNSStorage.sol +++ b/contracts/discovery/GNSStorage.sol @@ -3,12 +3,13 @@ pragma solidity ^0.7.6; pragma abicoder v2; +import "../base/SubgraphNFT.sol"; import "../governance/Managed.sol"; import "./erc1056/IEthereumDIDRegistry.sol"; import "./IGNS.sol"; -contract GNSV1Storage is Managed { +abstract contract GNSV1Storage is Managed { // -- State -- // In parts per hundred @@ -17,17 +18,31 @@ contract GNSV1Storage is Managed { // Bonding curve formula address public bondingCurve; - // graphAccountID => subgraphNumber => subgraphDeploymentID - // subgraphNumber = A number associated to a graph accounts deployed subgraph. This - // is used to point to a subgraphID (graphAccountID + subgraphNumber) - mapping(address => mapping(uint256 => bytes32)) public subgraphs; + // Stores what subgraph deployment a particular legacy subgraph targets + // A subgraph is defined by (graphAccountID, subgraphNumber) + // A subgraph can target one subgraph deployment (bytes32 hash) + // (graphAccountID, subgraphNumber) => subgraphDeploymentID + mapping(address => mapping(uint256 => bytes32)) internal legacySubgraphs; - // graphAccountID => subgraph deployment counter - mapping(address => uint256) public graphAccountSubgraphNumbers; + // Every time an account creates a subgraph it increases a per-account sequence ID + // account => seqID + mapping(address => uint256) public nextAccountSeqID; - // graphAccountID => subgraphNumber => NameCurationPool - mapping(address => mapping(uint256 => IGNS.NameCurationPool)) public nameSignals; + // Stores all the signal deposited on a legacy subgraph + // (graphAccountID, subgraphNumber) => SubgraphData + mapping(address => mapping(uint256 => IGNS.SubgraphData)) public legacySubgraphData; - // ERC-1056 contract reference - IEthereumDIDRegistry public erc1056Registry; + // [DEPRECATED] ERC-1056 contract reference + // This contract is used for managing identities + IEthereumDIDRegistry private __DEPRECATED_erc1056Registry; +} + +abstract contract GNSV2Storage is GNSV1Storage, SubgraphNFT { + // Use it whenever a legacy (v1) subgraph NFT was claimed to maintain compatibility + // Keep a reference from subgraphID => (graphAccount, subgraphNumber) + mapping(uint256 => IGNS.LegacySubgraphKey) public legacySubgraphKeys; + + // Store data for all NFT-based (v2) subgraphs + // subgraphID => SubgraphData + mapping(uint256 => IGNS.SubgraphData) public subgraphs; } diff --git a/contracts/discovery/IGNS.sol b/contracts/discovery/IGNS.sol index 8bb1ac773..02926bb11 100644 --- a/contracts/discovery/IGNS.sol +++ b/contracts/discovery/IGNS.sol @@ -5,9 +5,9 @@ pragma solidity ^0.7.6; interface IGNS { // -- Pool -- - struct NameCurationPool { - uint256 vSignal; // The token of the subgraph deployment bonding curve - uint256 nSignal; // The token of the name curation bonding curve + struct SubgraphData { + uint256 vSignal; // The token of the subgraph-deployment bonding curve + uint256 nSignal; // The token of the subgraph bonding curve mapping(address => uint256) curatorNSignal; bytes32 subgraphDeploymentID; uint32 reserveRatio; @@ -15,12 +15,19 @@ interface IGNS { uint256 withdrawableGRT; } + struct LegacySubgraphKey { + address account; + uint256 accountSeqID; + } + // -- Configuration -- function approveAll() external; function setOwnerTaxPercentage(uint32 _ownerTaxPercentage) external; + function setTokenDescriptor(address _tokenDescriptor) external; + // -- Publishing -- function setDefaultName( @@ -30,53 +37,45 @@ interface IGNS { string calldata _name ) external; - function updateSubgraphMetadata( - address _graphAccount, - uint256 _subgraphNumber, - bytes32 _subgraphMetadata - ) external; + function updateSubgraphMetadata(uint256 _subgraphID, bytes32 _subgraphMetadata) external; function publishNewSubgraph( - address _graphAccount, bytes32 _subgraphDeploymentID, bytes32 _versionMetadata, bytes32 _subgraphMetadata ) external; function publishNewVersion( - address _graphAccount, - uint256 _subgraphNumber, + uint256 _subgraphID, bytes32 _subgraphDeploymentID, bytes32 _versionMetadata ) external; - function deprecateSubgraph(address _graphAccount, uint256 _subgraphNumber) external; + function deprecateSubgraph(uint256 _subgraphID) external; // -- Curation -- - function mintNSignal( - address _graphAccount, - uint256 _subgraphNumber, + function mintSignal( + uint256 _subgraphID, uint256 _tokensIn, uint256 _nSignalOutMin ) external; - function burnNSignal( - address _graphAccount, - uint256 _subgraphNumber, + function burnSignal( + uint256 _subgraphID, uint256 _nSignal, uint256 _tokensOutMin ) external; - function withdraw(address _graphAccount, uint256 _subgraphNumber) external; + function withdraw(uint256 _subgraphID) external; // -- Getters -- - function tokensToNSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _tokensIn - ) + function subgraphSignal(uint256 _subgraphID) external view returns (uint256); + + function subgraphTokens(uint256 _subgraphID) external view returns (uint256); + + function tokensToNSignal(uint256 _subgraphID, uint256 _tokensIn) external view returns ( @@ -85,32 +84,25 @@ interface IGNS { uint256 ); - function nSignalToTokens( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _nSignalIn - ) external view returns (uint256, uint256); - - function vSignalToNSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _vSignalIn - ) external view returns (uint256); + function nSignalToTokens(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256, uint256); - function nSignalToVSignal( - address _graphAccount, - uint256 _subgraphNumber, - uint256 _nSignalIn - ) external view returns (uint256); + function vSignalToNSignal(uint256 _subgraphID, uint256 _vSignalIn) + external + view + returns (uint256); - function getCuratorNSignal( - address _graphAccount, - uint256 _subgraphNumber, - address _curator - ) external view returns (uint256); + function nSignalToVSignal(uint256 _subgraphID, uint256 _nSignalIn) + external + view + returns (uint256); - function isPublished(address _graphAccount, uint256 _subgraphNumber) + function getCuratorSignal(uint256 _subgraphID, address _curator) external view - returns (bool); + returns (uint256); + + function isPublished(uint256 _subgraphID) external view returns (bool); } diff --git a/graph.config.yml b/graph.config.yml index 9e69ccb70..65741fc68 100644 --- a/graph.config.yml +++ b/graph.config.yml @@ -63,7 +63,7 @@ contracts: init: controller: "${{Controller.address}}" bondingCurve: "${{BancorFormula.address}}" - didRegistry: "${{EthereumDIDRegistry.address}}" + tokenDescriptor: "${{SubgraphNFTDescriptor.address}}" calls: - fn: "approveAll" Staking: diff --git a/test/gns.test.ts b/test/gns.test.ts index 00817dc3f..b5e59a646 100644 --- a/test/gns.test.ts +++ b/test/gns.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai' import { ethers, ContractTransaction, BigNumber, Event } from 'ethers' +import { solidityKeccak256 } from 'ethers/lib/utils' import { GNS } from '../build/types/GNS' import { GraphToken } from '../build/types/GraphToken' @@ -9,21 +10,37 @@ import { getAccounts, randomHexBytes, Account, toGRT } from './lib/testHelpers' import { NetworkFixture } from './lib/fixtures' import { toBN, formatGRT } from './lib/testHelpers' -interface Subgraph { - graphAccount: Account +const { AddressZero } = ethers.constants + +// Entities +interface PublishSubgraph { subgraphDeploymentID: string - subgraphNumber: BigNumber versionMetadata: string subgraphMetadata: string } +interface Subgraph { + vSignal: BigNumber + nSignal: BigNumber + subgraphDeploymentID: string + reserveRatio: number + disabled: boolean + withdrawableGRT: BigNumber + id?: string +} + interface AccountDefaultName { name: string nameIdentifier: string } +// Utils + +const DEFAULT_RESERVE_RATIO = 1000000 const toFloat = (n: BigNumber) => parseFloat(formatGRT(n)) const toRound = (n: number) => n.toFixed(12) +const buildSubgraphID = (account: string, seqID: BigNumber): string => + solidityKeccak256(['address', 'uint256'], [account, seqID]) describe('GNS', () => { let me: Account @@ -40,16 +57,14 @@ describe('GNS', () => { const tokens10000 = toGRT('10000') const tokens100000 = toGRT('100000') const curationTaxPercentage = 50000 - let subgraph0: Subgraph - let subgraph1: Subgraph + + let newSubgraph0: PublishSubgraph + let newSubgraph1: PublishSubgraph let defaultName: AccountDefaultName - const createSubgraph = (account: Account, subgraphNumber: string): Subgraph => { + const buildSubgraph = (): PublishSubgraph => { return { - graphAccount: account, subgraphDeploymentID: randomHexBytes(), - subgraphNumber: BigNumber.from(subgraphNumber), - versionMetadata: randomHexBytes(), subgraphMetadata: randomHexBytes(), } @@ -62,9 +77,9 @@ describe('GNS', () => { } } - const getTokensAndVSignal = async (subgraphID: string): Promise> => { - const curationPool = await curation.pools(subgraphID) - const vSignal = await curation.getCurationPoolSignal(subgraphID) + const getTokensAndVSignal = async (subgraphDeploymentID: string): Promise> => { + const curationPool = await curation.pools(subgraphDeploymentID) + const vSignal = await curation.getCurationPoolSignal(subgraphDeploymentID) return [curationPool.tokens, vSignal] } @@ -125,60 +140,59 @@ describe('GNS', () => { const publishNewSubgraph = async ( account: Account, - graphAccount: string, - subgraphNumber: number, - subgraphToPublish = subgraph0, // Defaults to subgraph created in before() - ): Promise => { + newSubgraph: PublishSubgraph, // Defaults to subgraph created in before() + ): Promise => { + const subgraphID = buildSubgraphID(account.address, await gns.nextAccountSeqID(account.address)) + + // Send tx const tx = gns .connect(account.signer) .publishNewSubgraph( - graphAccount, - subgraphToPublish.subgraphDeploymentID, - subgraphToPublish.versionMetadata, - subgraphToPublish.subgraphMetadata, + newSubgraph.subgraphDeploymentID, + newSubgraph.versionMetadata, + newSubgraph.subgraphMetadata, ) + + // Check events await expect(tx) .emit(gns, 'SubgraphPublished') - .withArgs( - subgraphToPublish.graphAccount.address, - subgraphNumber, - subgraphToPublish.subgraphDeploymentID, - subgraphToPublish.versionMetadata, - ) - .emit(gns, 'NameSignalEnabled') - .withArgs(graphAccount, subgraphNumber, subgraphToPublish.subgraphDeploymentID, 1000000) + .withArgs(subgraphID, newSubgraph.subgraphDeploymentID, DEFAULT_RESERVE_RATIO) .emit(gns, 'SubgraphMetadataUpdated') - .withArgs( - subgraphToPublish.graphAccount.address, - subgraphNumber, - subgraphToPublish.subgraphMetadata, - ) - - const pool = await gns.nameSignals(graphAccount, subgraphNumber) - const reserveRatio = pool[3] - expect(reserveRatio).eq(1000000) - return tx + .withArgs(subgraphID, newSubgraph.subgraphMetadata) + .emit(gns, 'SubgraphVersionUpdated') + .withArgs(subgraphID, newSubgraph.subgraphDeploymentID, newSubgraph.versionMetadata) + + // Check state + const subgraph = await gns.subgraphs(subgraphID) + expect(subgraph.vSignal).eq(0) + expect(subgraph.nSignal).eq(0) + expect(subgraph.subgraphDeploymentID).eq(newSubgraph.subgraphDeploymentID) + expect(subgraph.reserveRatio).eq(DEFAULT_RESERVE_RATIO) + expect(subgraph.disabled).eq(false) + expect(subgraph.withdrawableGRT).eq(0) + + // Check NFT issuance + const owner = await gns.ownerOf(subgraphID) + expect(owner).eq(account.address) + + return { ...subgraph, id: subgraphID } } const publishNewVersion = async ( account: Account, - graphAccount: string, - subgraphNumber: number, - subgraphToPublish = subgraph0, // Defaults to subgraph created in before() + subgraphID: string, + newSubgraph: PublishSubgraph, ) => { - // Before stats for the old vSignal curve + // Before state const ownerTaxPercentage = await gns.ownerTaxPercentage() const curationTaxPercentage = await curation.curationTaxPercentage() - // Before stats for the name curve - const namePoolBefore = await gns.nameSignals(graphAccount, subgraphNumber) + const beforeSubgraph = await gns.subgraphs(subgraphID) // Check what selling all nSignal, which == selling all vSignal, should return for tokens // NOTE - no tax on burning on nSignal - const { 1: tokensReceivedEstimate } = await gns.nSignalToTokens( - graphAccount, - subgraphNumber, - namePoolBefore.nSignal, - ) + const tokensReceivedEstimate = beforeSubgraph.nSignal.gt(0) + ? (await gns.nSignalToTokens(subgraphID, beforeSubgraph.nSignal))[1] + : toBN(0) // Example: // Deposit 100, 5 is taxed, 95 GRT in curve // Upgrade - calculate 5% tax on 95 --> 4.75 GRT @@ -192,98 +206,85 @@ describe('GNS', () => { const taxOnOriginal = tokensReceivedEstimate.mul(curationTaxPercentage).div(MAX_PPM) const totalWithoutOwnerTax = tokensReceivedEstimate.sub(taxOnOriginal) const ownerTax = taxOnOriginal.mul(ownerTaxPercentage).div(MAX_PPM) - const totalWithOwnerTax = totalWithoutOwnerTax.add(ownerTax) - const totalAdjustedUp = totalWithOwnerTax.mul(MAX_PPM).div(MAX_PPM - curationTaxPercentage) // Re-estimate amount of signal to get considering the owner tax paid by the owner - const { 0: newVSignalEstimate, 1: newCurationTaxEstimate } = await curation.tokensToSignal( - subgraphToPublish.subgraphDeploymentID, - totalAdjustedUp, - ) - // Send transaction + const { 0: newVSignalEstimate, 1: newCurationTaxEstimate } = beforeSubgraph.nSignal.gt(0) + ? await curation.tokensToSignal(newSubgraph.subgraphDeploymentID, totalAdjustedUp) + : [toBN(0), toBN(0)] + + // Send tx const tx = gns .connect(account.signer) - .publishNewVersion( - graphAccount, - subgraphNumber, - subgraphToPublish.subgraphDeploymentID, - subgraphToPublish.versionMetadata, - ) + .publishNewVersion(subgraphID, newSubgraph.subgraphDeploymentID, newSubgraph.versionMetadata) await expect(tx) - .emit(gns, 'SubgraphPublished') - .withArgs( - subgraphToPublish.graphAccount.address, - subgraphNumber, - subgraphToPublish.subgraphDeploymentID, - subgraphToPublish.versionMetadata, - ) - .emit(gns, 'NameSignalUpgrade') - .withArgs( - graphAccount, - subgraphNumber, - newVSignalEstimate, - totalAdjustedUp, - subgraphToPublish.subgraphDeploymentID, - ) + .emit(gns, 'SubgraphUpgraded') + .withArgs(subgraphID, newVSignalEstimate, totalAdjustedUp, newSubgraph.subgraphDeploymentID) + .emit(gns, 'SubgraphVersionUpdated') + .withArgs(subgraphID, newSubgraph.subgraphDeploymentID, newSubgraph.versionMetadata) // Check curation vSignal old are set to zero - const [tokensAfterOldCuration, vSignalAfterOldCuration] = await getTokensAndVSignal( - subgraph0.subgraphDeploymentID, + const [afterTokensOldCuration, afterVSignalOldCuration] = await getTokensAndVSignal( + beforeSubgraph.subgraphDeploymentID, ) - expect(tokensAfterOldCuration).eq(0) - expect(vSignalAfterOldCuration).eq(0) + expect(afterTokensOldCuration).eq(0) + expect(afterVSignalOldCuration).eq(0) - // Check the vSignal of the new curation curve, amd tokens - const [tokensAfterNewCurve, vSignalAfterNewCurve] = await getTokensAndVSignal( - subgraphToPublish.subgraphDeploymentID, + // Check the vSignal of the new curation curve, and tokens + const [afterTokensNewCurve, afterVSignalNewCurve] = await getTokensAndVSignal( + newSubgraph.subgraphDeploymentID, ) - expect(tokensAfterNewCurve).eq(totalAdjustedUp.sub(newCurationTaxEstimate)) - expect(vSignalAfterNewCurve).eq(newVSignalEstimate) + expect(afterTokensNewCurve).eq(totalAdjustedUp.sub(newCurationTaxEstimate)) + expect(afterVSignalNewCurve).eq(newVSignalEstimate) // Check the nSignal pool - const namePoolAfter = await gns.nameSignals(graphAccount, subgraphNumber) - expect(namePoolAfter.vSignal).eq(vSignalAfterNewCurve).eq(newVSignalEstimate) - expect(namePoolAfter.nSignal).eq(namePoolBefore.nSignal) // should not change - expect(namePoolAfter.subgraphDeploymentID).eq(subgraphToPublish.subgraphDeploymentID) + const afterSubgraph = await gns.subgraphs(subgraphID) + expect(afterSubgraph.vSignal).eq(afterVSignalNewCurve).eq(newVSignalEstimate) + expect(afterSubgraph.nSignal).eq(beforeSubgraph.nSignal) // should not change + expect(afterSubgraph.subgraphDeploymentID).eq(newSubgraph.subgraphDeploymentID) + + // Check NFT should not change owner + const owner = await gns.ownerOf(subgraphID) + expect(owner).eq(account.address) return tx } - const deprecateSubgraph = async ( - account: Account, - graphAccount: string, - subgraphNumber0: number, - ) => { - const [tokensBefore] = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) + const deprecateSubgraph = async (account: Account, subgraphID: string) => { + // Before state + const beforeSubgraph = await gns.subgraphs(subgraphID) + const [beforeTokens] = await getTokensAndVSignal(beforeSubgraph.subgraphDeploymentID) + // We can use the whole amount, since in this test suite all vSignal is used to be staked on nSignal const ownerBalanceBefore = await grt.balanceOf(account.address) - const tx = gns.connect(account.signer).deprecateSubgraph(graphAccount, subgraphNumber0) - await expect(tx).emit(gns, 'SubgraphDeprecated').withArgs(subgraph0.graphAccount.address, 0) - await expect(tx) - .emit(gns, 'NameSignalDisabled') - .withArgs(graphAccount, subgraphNumber0, tokensBefore) + // Send tx + const tx = gns.connect(account.signer).deprecateSubgraph(subgraphID) + await expect(tx).emit(gns, 'SubgraphDeprecated').withArgs(subgraphID, beforeTokens) - const deploymentID = await gns.subgraphs(subgraph0.graphAccount.address, 0) - expect(ethers.constants.HashZero).eq(deploymentID) + // After state + const afterSubgraph = await gns.subgraphs(subgraphID) + // Check marked as deprecated + expect(afterSubgraph.disabled).eq(true) + // Signal for the deployment must be all burned + expect(afterSubgraph.vSignal.eq(toBN('0'))) + // Cleanup reserve ratio + expect(afterSubgraph.reserveRatio).eq(0) + // Should be equal since owner pays curation tax + expect(afterSubgraph.withdrawableGRT).eq(beforeTokens) - // Check that vSignal is set to 0 - const poolAfter = await gns.nameSignals(graphAccount, subgraphNumber0) - const poolVSignalAfter = poolAfter.vSignal - expect(poolVSignalAfter.eq(toBN('0'))) + // Check balance of GNS increased by curation tax from owner being added + const afterGNSBalance = await grt.balanceOf(gns.address) + expect(afterGNSBalance).eq(afterSubgraph.withdrawableGRT) // Check that the owner balance decreased by the curation tax const ownerBalanceAfter = await grt.balanceOf(account.address) expect(ownerBalanceBefore.eq(ownerBalanceAfter)) - // Should be equal since owner pays curation tax - expect(poolAfter.withdrawableGRT).eq(tokensBefore) - // Check that deprecated is true - expect(poolAfter.disabled).eq(true) - // Check balance of gns increase by curation tax from owner being added - const gnsBalanceAfter = await grt.balanceOf(gns.address) - expect(gnsBalanceAfter).eq(poolAfter.withdrawableGRT) + + // Check NFT was burned + await expect(gns.ownerOf(subgraphID)).revertedWith('ERC721: owner query for nonexistent token') + return tx } @@ -295,9 +296,9 @@ describe('GNS', () => { newSubgraphDeplyomentID: string, ): Promise => { // Before stats for the old vSignal curve - const tokensBeforeVSigOldCuration = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) - const tokensBeforeOldCuration = tokensBeforeVSigOldCuration[0] - const vSigBeforeOldCuration = tokensBeforeVSigOldCuration[1] + const beforeTokensVSigOldCuration = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) + const beforeTokensOldCuration = beforeTokensVSigOldCuration[0] + const beforeVSignalOldCuration = beforeTokensVSigOldCuration[1] // Before stats for the name curve const poolBefore = await gns.nameSignals(graphAccount, subgraphNumber0) @@ -337,17 +338,17 @@ describe('GNS', () => { ) // Check curation vSignal old was lowered and tokens too - const [tokensAfterOldCuration, vSigAfterOldCuration] = await getTokensAndVSignal( + const [afterTokensOldCuration, vSigAfterOldCuration] = await getTokensAndVSignal( subgraph0.subgraphDeploymentID, ) - expect(tokensAfterOldCuration).eq(tokensBeforeOldCuration.sub(upgradeTokenReturn)) - expect(vSigAfterOldCuration).eq(vSigBeforeOldCuration.sub(vSignalBurnEstimate)) + expect(afterTokensOldCuration).eq(beforeTokensOldCuration.sub(upgradeTokenReturn)) + expect(vSigAfterOldCuration).eq(beforeVSignalOldCuration.sub(vSignalBurnEstimate)) // Check the vSignal of the new curation curve, amd tokens - const [tokensAfterNewCurve, vSigAfterNewCurve] = await getTokensAndVSignal( + const [afterTokensNewCurve, vSigAfterNewCurve] = await getTokensAndVSignal( newSubgraphDeplyomentID, ) - expect(tokensAfterNewCurve).eq(upgradeTokenReturn) + expect(afterTokensNewCurve).eq(upgradeTokenReturn) expect(vSigAfterNewCurve).eq(newVSignalEstimate) // Check the nSignal pool @@ -363,142 +364,103 @@ describe('GNS', () => { } */ - const mintNSignal = async ( + const mintSignal = async ( account: Account, - graphAccount: string, - subgraphNumber0: number, - graphTokens: BigNumber, + subgraphID: string, + tokensIn: BigNumber, ): Promise => { // Before state - const [tokensBefore, vSignalBefore] = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) - const namePoolBefore = await gns.nameSignals(graphAccount, subgraphNumber0) + const beforeSubgraph = await gns.subgraphs(subgraphID) + const [beforeTokens, beforeVSignal] = await getTokensAndVSignal( + beforeSubgraph.subgraphDeploymentID, + ) // Deposit const { 0: vSignalExpected, 1: nSignalExpected, 2: curationTax, - } = await gns.tokensToNSignal(graphAccount, subgraphNumber0, graphTokens) - const tx = gns - .connect(account.signer) - .mintNSignal(graphAccount, subgraphNumber0, graphTokens, 0) + } = await gns.tokensToNSignal(subgraphID, tokensIn) + const tx = gns.connect(account.signer).mintSignal(subgraphID, tokensIn, 0) await expect(tx) - .emit(gns, 'NSignalMinted') - .withArgs( - graphAccount, - subgraphNumber0, - account.address, - nSignalExpected, - vSignalExpected, - graphTokens, - ) + .emit(gns, 'SignalMinted') + .withArgs(subgraphID, account.address, nSignalExpected, vSignalExpected, tokensIn) // After state - const [tokensAfter, vSignalAfter] = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) - const namePoolAfter = await gns.nameSignals(graphAccount, subgraphNumber0) + const afterSubgraph = await gns.subgraphs(subgraphID) + const [afterTokens, afterVSignal] = await getTokensAndVSignal( + afterSubgraph.subgraphDeploymentID, + ) - expect(tokensAfter).eq(tokensBefore.add(graphTokens.sub(curationTax))) - expect(vSignalAfter).eq(vSignalBefore.add(vSignalExpected)) - expect(namePoolAfter.nSignal).eq(namePoolBefore.nSignal.add(nSignalExpected)) - expect(namePoolAfter.vSignal).eq(vSignalBefore.add(vSignalExpected)) + // Check state + expect(afterTokens).eq(beforeTokens.add(tokensIn.sub(curationTax))) + expect(afterVSignal).eq(beforeVSignal.add(vSignalExpected)) + expect(afterSubgraph.nSignal).eq(beforeSubgraph.nSignal.add(nSignalExpected)) + expect(afterSubgraph.vSignal).eq(beforeVSignal.add(vSignalExpected)) return tx } - const burnNSignal = async ( - account: Account, - graphAccount: string, - subgraphNumber0: number, - ): Promise => { - // Before checks - const [tokensBefore, vSigBefore] = await getTokensAndVSignal(subgraph0.subgraphDeploymentID) - const namePoolBefore = await gns.nameSignals(graphAccount, subgraphNumber0) - const usersNSignalBefore = await gns.getCuratorNSignal( - graphAccount, - subgraphNumber0, - account.address, + const burnSignal = async (account: Account, subgraphID: string): Promise => { + // Before state + const beforeSubgraph = await gns.subgraphs(subgraphID) + const [beforeTokens, beforeVSignal] = await getTokensAndVSignal( + beforeSubgraph.subgraphDeploymentID, ) + const beforeUsersNSignal = await gns.getCuratorSignal(subgraphID, account.address) // Withdraw const { 0: vSignalExpected, 1: tokensExpected } = await gns.nSignalToTokens( - graphAccount, - subgraphNumber0, - usersNSignalBefore, + subgraphID, + beforeUsersNSignal, ) - // Do withdraw tx - const tx = gns - .connect(account.signer) - .burnNSignal(graphAccount, subgraphNumber0, usersNSignalBefore, 0) + // Send tx + const tx = gns.connect(account.signer).burnSignal(subgraphID, beforeUsersNSignal, 0) await expect(tx) - .emit(gns, 'NSignalBurned') - .withArgs( - graphAccount, - subgraphNumber0, - account.address, - usersNSignalBefore, - vSignalExpected, - tokensExpected, - ) + .emit(gns, 'SignalBurned') + .withArgs(subgraphID, account.address, beforeUsersNSignal, vSignalExpected, tokensExpected) - // After checks - const [tokensAfter, vSignalCurationAfter] = await getTokensAndVSignal( - subgraph0.subgraphDeploymentID, + // After state + const afterSubgraph = await gns.subgraphs(subgraphID) + const [afterTokens, afterVSignalCuration] = await getTokensAndVSignal( + afterSubgraph.subgraphDeploymentID, ) - const namePoolAfter = await gns.nameSignals(graphAccount, subgraphNumber0) - expect(tokensAfter).eq(tokensBefore.sub(tokensExpected)) - expect(vSignalCurationAfter).eq(vSigBefore.sub(vSignalExpected)) - expect(namePoolAfter.nSignal).eq(namePoolBefore.nSignal.sub(usersNSignalBefore)) + // Check state + expect(afterTokens).eq(beforeTokens.sub(tokensExpected)) + expect(afterVSignalCuration).eq(beforeVSignal.sub(vSignalExpected)) + expect(afterSubgraph.nSignal).eq(beforeSubgraph.nSignal.sub(beforeUsersNSignal)) return tx } - const withdraw = async ( - account: Account, - graphAccount: string, - subgraphNumber0: number, - ): Promise => { - const curatorNSignalBefore = await gns.getCuratorNSignal( - graphAccount, - subgraphNumber0, - account.address, - ) - const poolBefore = await gns.nameSignals(graphAccount, subgraphNumber0) - const gnsBalanceBefore = await grt.balanceOf(gns.address) - const tokensEstimate = poolBefore.withdrawableGRT - .mul(curatorNSignalBefore) - .div(poolBefore.nSignal) - - // Run tx - const tx = gns.connect(account.signer).withdraw(graphAccount, subgraphNumber0) + const withdraw = async (account: Account, subgraphID: string): Promise => { + // Before state + const beforeCuratorNSignal = await gns.getCuratorSignal(subgraphID, account.address) + const beforeSubgraph = await gns.subgraphs(subgraphID) + const beforeGNSBalance = await grt.balanceOf(gns.address) + const tokensEstimate = beforeSubgraph.withdrawableGRT + .mul(beforeCuratorNSignal) + .div(beforeSubgraph.nSignal) + + // Send tx + const tx = gns.connect(account.signer).withdraw(subgraphID) await expect(tx) .emit(gns, 'GRTWithdrawn') - .withArgs( - graphAccount, - subgraphNumber0, - account.address, - curatorNSignalBefore, - tokensEstimate, - ) + .withArgs(subgraphID, account.address, beforeCuratorNSignal, tokensEstimate) // curator nSignal should be updated - const curatorNSignalAfter = await gns.getCuratorNSignal( - graphAccount, - subgraphNumber0, - account.address, - ) - - expect(curatorNSignalAfter).eq(toBN(0)) + const afterCuratorNSignal = await gns.getCuratorSignal(subgraphID, account.address) + expect(afterCuratorNSignal).eq(toBN(0)) // overall n signal should be updated - const poolAfter = await gns.nameSignals(graphAccount, subgraphNumber0) - expect(poolAfter.nSignal).eq(poolBefore.nSignal.sub(curatorNSignalBefore)) - // withdrawableGRT should be updated + const afterSubgraph = await gns.subgraphs(subgraphID) + expect(afterSubgraph.nSignal).eq(beforeSubgraph.nSignal.sub(beforeCuratorNSignal)) // Token balance should be updated - const gnsBalanceAfter = await grt.balanceOf(gns.address) - expect(gnsBalanceAfter).eq(gnsBalanceBefore.sub(tokensEstimate)) + const afterGNSBalance = await grt.balanceOf(gns.address) + expect(afterGNSBalance).eq(beforeGNSBalance.sub(tokensEstimate)) return tx } @@ -507,8 +469,8 @@ describe('GNS', () => { ;[me, other, governor] = await getAccounts() fixture = new NetworkFixture() ;({ grt, curation, gns } = await fixture.load(governor.signer)) - subgraph0 = createSubgraph(me, '0') - subgraph1 = createSubgraph(me, '1') + newSubgraph0 = buildSubgraph() + newSubgraph1 = buildSubgraph() defaultName = createDefaultName('graph') // Give some funds to the signers and approve gns contract to use funds on signers behalf await grt.connect(governor.signer).mint(me.address, tokens100000) @@ -529,6 +491,47 @@ describe('GNS', () => { await fixture.tearDown() }) + describe('Configuration', async function () { + describe('setOwnerTaxPercentage', function () { + const newValue = 10 + + it('should set `ownerTaxPercentage`', async function () { + // Can set if allowed + await gns.connect(governor.signer).setOwnerTaxPercentage(newValue) + expect(await gns.ownerTaxPercentage()).eq(newValue) + }) + + it('reject set `ownerTaxPercentage` if out of bounds', async function () { + const tx = gns.connect(governor.signer).setOwnerTaxPercentage(1000001) + await expect(tx).revertedWith('Owner tax must be MAX_PPM or less') + }) + + it('reject set `ownerTaxPercentage` if not allowed', async function () { + const tx = gns.connect(me.signer).setOwnerTaxPercentage(newValue) + await expect(tx).revertedWith('Caller must be Controller governor') + }) + }) + + describe('setTokenDescriptor', function () { + it('should set `tokenDescriptor`', async function () { + const newTokenDescriptor = gns.address // I just use any contract address + const tx = gns.connect(governor.signer).setTokenDescriptor(newTokenDescriptor) + await expect(tx).emit(gns, 'TokenDescriptorUpdated').withArgs(newTokenDescriptor) + expect(await gns.tokenDescriptor()).eq(newTokenDescriptor) + }) + + it('revert set to empty address', async function () { + const tx = gns.connect(governor.signer).setTokenDescriptor(AddressZero) + await expect(tx).revertedWith('NFT: Invalid token descriptor') + }) + + it('revert set to non-contract', async function () { + const tx = gns.connect(governor.signer).setTokenDescriptor(randomHexBytes(20)) + await expect(tx).revertedWith('NFT: Invalid token descriptor') + }) + }) + }) + describe('Publishing names and versions', function () { describe('setDefaultName', function () { it('setDefaultName emits the event', async function () { @@ -537,279 +540,263 @@ describe('GNS', () => { .setDefaultName(me.address, 0, defaultName.nameIdentifier, defaultName.name) await expect(tx) .emit(gns, 'SetDefaultName') - .withArgs(subgraph0.graphAccount.address, 0, defaultName.nameIdentifier, defaultName.name) + .withArgs(me.address, 0, defaultName.nameIdentifier, defaultName.name) }) it('setDefaultName fails if not owner', async function () { const tx = gns .connect(other.signer) .setDefaultName(me.address, 0, defaultName.nameIdentifier, defaultName.name) - await expect(tx).revertedWith('GNS: Only graph account owner can call') + await expect(tx).revertedWith('GNS: Only you can set your name') }) }) describe('updateSubgraphMetadata', function () { + let subgraph: Subgraph + + beforeEach(async function () { + subgraph = await publishNewSubgraph(me, newSubgraph0) + }) + it('updateSubgraphMetadata emits the event', async function () { const tx = gns .connect(me.signer) - .updateSubgraphMetadata(me.address, 0, subgraph0.subgraphMetadata) + .updateSubgraphMetadata(subgraph.id, newSubgraph0.subgraphMetadata) await expect(tx) .emit(gns, 'SubgraphMetadataUpdated') - .withArgs(subgraph0.graphAccount.address, 0, subgraph0.subgraphMetadata) + .withArgs(subgraph.id, newSubgraph0.subgraphMetadata) }) it('updateSubgraphMetadata fails if not owner', async function () { const tx = gns .connect(other.signer) - .updateSubgraphMetadata(me.address, 0, subgraph0.subgraphMetadata) - await expect(tx).revertedWith('GNS: Only graph account owner can call') + .updateSubgraphMetadata(subgraph.id, newSubgraph0.subgraphMetadata) + await expect(tx).revertedWith('GNS: Must be authorized') }) }) + describe('isPublished', function () { it('should return if the subgraph is published', async function () { - expect(await gns.isPublished(subgraph0.graphAccount.address, 0)).eq(false) - await publishNewSubgraph(me, me.address, 0) - expect(await gns.isPublished(subgraph0.graphAccount.address, 0)).eq(true) + const subgraphID = buildSubgraphID(me.address, toBN(0)) + expect(await gns.isPublished(subgraphID)).eq(false) + await publishNewSubgraph(me, newSubgraph0) + expect(await gns.isPublished(subgraphID)).eq(true) }) }) describe('publishNewSubgraph', async function () { it('should publish a new subgraph and first version with it', async function () { - await publishNewSubgraph(me, me.address, 0) - // State updated - const deploymentID = await gns.subgraphs(subgraph0.graphAccount.address, 0) - expect(subgraph0.subgraphDeploymentID).eq(deploymentID) + await publishNewSubgraph(me, newSubgraph0) }) it('should publish a new subgraph with an incremented value', async function () { - await publishNewSubgraph(me, me.address, 0) - await publishNewSubgraph(me, me.address, 1, subgraph1) - const deploymentID = await gns.subgraphs(subgraph1.graphAccount.address, 1) - expect(subgraph1.subgraphDeploymentID).eq(deploymentID) - }) - - it('should reject publish if not sent from owner', async function () { - const tx = gns - .connect(other.signer) - .publishNewSubgraph( - subgraph0.graphAccount.address, - ethers.constants.HashZero, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, - ) - await expect(tx).revertedWith('GNS: Only graph account owner can call') + const subgraph1 = await publishNewSubgraph(me, newSubgraph0) + const subgraph2 = await publishNewSubgraph(me, newSubgraph1) + expect(subgraph1.id).not.eq(subgraph2.id) }) it('should prevent subgraphDeploymentID of 0 to be used', async function () { const tx = gns .connect(me.signer) .publishNewSubgraph( - subgraph0.graphAccount.address, ethers.constants.HashZero, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, + newSubgraph0.versionMetadata, + newSubgraph0.subgraphMetadata, ) await expect(tx).revertedWith('GNS: Cannot set deploymentID to 0 in publish') }) }) describe('publishNewVersion', async function () { + let subgraph: Subgraph + beforeEach(async () => { - await publishNewSubgraph(me, me.address, 0) - await mintNSignal(me, me.address, 0, tokens10000) + subgraph = await publishNewSubgraph(me, newSubgraph0) + await mintSignal(me, subgraph.id, tokens10000) }) it('should publish a new version on an existing subgraph', async function () { - await publishNewVersion(me, me.address, 0, subgraph1) + await publishNewVersion(me, subgraph.id, newSubgraph1) + }) + + it('should publish a new version on an existing subgraph with no current signal', async function () { + const emptySignalSubgraph = await publishNewSubgraph(me, buildSubgraph()) + await publishNewVersion(me, emptySignalSubgraph.id, newSubgraph1) }) it('should reject a new version with the same subgraph deployment ID', async function () { const tx = gns .connect(me.signer) .publishNewVersion( - subgraph0.graphAccount.address, - 0, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, + subgraph.id, + newSubgraph0.subgraphDeploymentID, + newSubgraph0.versionMetadata, ) await expect(tx).revertedWith( 'GNS: Cannot publish a new version with the same subgraph deployment ID', ) }) - it('should reject publishing a version to a numbered subgraph that does not exist', async function () { - const wrongNumberedSubgraph = 9999 + it('should reject publishing a version to a subgraph that does not exist', async function () { const tx = gns .connect(me.signer) .publishNewVersion( - subgraph1.graphAccount.address, - wrongNumberedSubgraph, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + randomHexBytes(32), + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) - await expect(tx).revertedWith( - 'GNS: Cannot update version if not published, or has been deprecated', - ) + await expect(tx).revertedWith('ERC721: owner query for nonexistent token') }) it('reject if not the owner', async function () { const tx = gns .connect(other.signer) .publishNewVersion( - subgraph1.graphAccount.address, - 0, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + subgraph.id, + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) - await expect(tx).revertedWith('GNS: Only graph account owner can call') + await expect(tx).revertedWith('GNS: Must be authorized') }) it('should fail when upgrade tries to point to a pre-curated', async function () { - await curation.connect(me.signer).mint(subgraph1.subgraphDeploymentID, tokens1000, 0) + // Curate directly to the deployment + await curation.connect(me.signer).mint(newSubgraph1.subgraphDeploymentID, tokens1000, 0) + + // Target a pre-curated subgraph deployment const tx = gns .connect(me.signer) .publishNewVersion( - me.address, - 0, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + subgraph.id, + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) await expect(tx).revertedWith( 'GNS: Owner cannot point to a subgraphID that has been pre-curated', ) }) - it('should fail when trying to upgrade when there is no nSignal', async function () { - await burnNSignal(me, me.address, 0) + it('should upgrade version when there is no signal with no signal migration', async function () { + await burnSignal(me, subgraph.id) const tx = gns .connect(me.signer) .publishNewVersion( - me.address, - 0, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + subgraph.id, + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) - await expect(tx).revertedWith( - 'GNS: There must be nSignal on this subgraph for curve math to work', - ) + await expect(tx) + .emit(gns, 'SubgraphVersionUpdated') + .withArgs(subgraph.id, newSubgraph1.subgraphDeploymentID, newSubgraph1.versionMetadata) }) it('should fail when subgraph is deprecated', async function () { - await deprecateSubgraph(me, me.address, 0) + await deprecateSubgraph(me, subgraph.id) const tx = gns .connect(me.signer) .publishNewVersion( - me.address, - 0, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + subgraph.id, + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) - await expect(tx).revertedWith( - 'GNS: Cannot update version if not published, or has been deprecated', - ) + // NOTE: deprecate burns the Subgraph NFT, when someone wants to publish a new version it won't find it + await expect(tx).revertedWith('ERC721: owner query for nonexistent token') }) }) describe('deprecateSubgraph', async function () { + let subgraph: Subgraph + beforeEach(async () => { - await publishNewSubgraph(me, me.address, 0) - await mintNSignal(me, me.address, 0, tokens10000) + subgraph = await publishNewSubgraph(me, newSubgraph0) + await mintSignal(me, subgraph.id, tokens10000) }) it('should deprecate a subgraph', async function () { - await deprecateSubgraph(me, me.address, 0) + await deprecateSubgraph(me, subgraph.id) }) it('should prevent a deprecated subgraph from being republished', async function () { - await deprecateSubgraph(me, me.address, 0) + await deprecateSubgraph(me, subgraph.id) const tx = gns .connect(me.signer) .publishNewVersion( - subgraph1.graphAccount.address, - 1, - subgraph1.subgraphDeploymentID, - subgraph1.versionMetadata, + subgraph.id, + newSubgraph1.subgraphDeploymentID, + newSubgraph1.versionMetadata, ) - await expect(tx).revertedWith( - 'Cannot update version if not published, or has been deprecated', - ) + // NOTE: deprecate burns the Subgraph NFT, when someone wants to publish a new version it won't find it + await expect(tx).revertedWith('ERC721: owner query for nonexistent token') }) it('reject if the subgraph does not exist', async function () { - const wrongNumberedSubgraph = 2340 - const tx = gns - .connect(me.signer) - .deprecateSubgraph(subgraph1.graphAccount.address, wrongNumberedSubgraph) - await expect(tx).revertedWith('GNS: Cannot deprecate a subgraph which does not exist') + const subgraphID = randomHexBytes(32) + const tx = gns.connect(me.signer).deprecateSubgraph(subgraphID) + await expect(tx).revertedWith('ERC721: owner query for nonexistent token') }) it('reject deprecate if not the owner', async function () { - const tx = gns - .connect(other.signer) - .deprecateSubgraph(subgraph0.graphAccount.address, subgraph0.subgraphNumber) - await expect(tx).revertedWith('GNS: Only graph account owner can call') + const tx = gns.connect(other.signer).deprecateSubgraph(subgraph.id) + await expect(tx).revertedWith('GNS: Must be authorized') }) }) }) - describe('Curating on names', async function () { - const subgraphNumber0 = 0 - describe('mintNSignal()', async function () { + describe('Curating on names', async function () { + describe('mintSignal()', async function () { it('should deposit into the name signal curve', async function () { - await publishNewSubgraph(me, me.address, subgraphNumber0) - await mintNSignal(other, me.address, subgraphNumber0, tokens10000) + const subgraph = await publishNewSubgraph(me, newSubgraph0) + await mintSignal(other, subgraph.id, tokens10000) }) it('should fail when name signal is disabled', async function () { - await publishNewSubgraph(me, me.address, subgraphNumber0) - await deprecateSubgraph(me, me.address, 0) - const tx = gns.connect(me.signer).mintNSignal(me.address, subgraphNumber0, tokens1000, 0) - await expect(tx).revertedWith('GNS: Cannot be disabled') + const subgraph = await publishNewSubgraph(me, newSubgraph0) + await deprecateSubgraph(me, subgraph.id) + const tx = gns.connect(me.signer).mintSignal(subgraph.id, tokens1000, 0) + await expect(tx).revertedWith('GNS: Must be active') }) it('should fail if you try to deposit on a non existing name', async function () { - const tx = gns.connect(me.signer).mintNSignal(me.address, subgraphNumber0, tokens1000, 0) - await expect(tx).revertedWith('GNS: Must deposit on a name signal that exists') + const subgraphID = randomHexBytes(32) + const tx = gns.connect(me.signer).mintSignal(subgraphID, tokens1000, 0) + await expect(tx).revertedWith('GNS: Must be active') }) it('reject minting if under slippage', async function () { // First publish the subgraph - await publishNewSubgraph(me, me.address, subgraphNumber0) + const subgraph = await publishNewSubgraph(me, newSubgraph0) // Set slippage to be 1 less than expected result to force reverting - const { 1: expectedNSignal } = await gns.tokensToNSignal( - me.address, - subgraphNumber0, - tokens1000, - ) + const { 1: expectedNSignal } = await gns.tokensToNSignal(subgraph.id, tokens1000) const tx = gns .connect(me.signer) - .mintNSignal(me.address, subgraphNumber0, tokens1000, expectedNSignal.add(1)) + .mintSignal(subgraph.id, tokens1000, expectedNSignal.add(1)) await expect(tx).revertedWith('Slippage protection') }) }) - describe('burnNSignal()', async function () { + describe('burnSignal()', async function () { + let subgraph: Subgraph + beforeEach(async () => { - await publishNewSubgraph(me, me.address, subgraphNumber0) - await mintNSignal(other, me.address, subgraphNumber0, tokens10000) + subgraph = await publishNewSubgraph(me, newSubgraph0) + await mintSignal(other, subgraph.id, tokens10000) }) it('should withdraw from the name signal curve', async function () { - await burnNSignal(other, me.address, subgraphNumber0) + await burnSignal(other, subgraph.id) }) it('should fail when name signal is disabled', async function () { - await deprecateSubgraph(me, me.address, 0) + await deprecateSubgraph(me, subgraph.id) // just test 1 since it will fail - const tx = gns.connect(me.signer).burnNSignal(me.address, subgraphNumber0, 1, 0) - await expect(tx).revertedWith('GNS: Cannot be disabled') + const tx = gns.connect(me.signer).burnSignal(subgraph.id, 1, 0) + await expect(tx).revertedWith('GNS: Must be active') }) it('should fail when the curator tries to withdraw more nSignal than they have', async function () { - const tx = gns.connect(me.signer).burnNSignal( - me.address, - subgraphNumber0, + const tx = gns.connect(me.signer).burnSignal( + subgraph.id, // 1000000 * 10^18 nSignal is a lot, and will cause fail toBN('1000000000000000000000000'), 0, @@ -819,54 +806,48 @@ describe('GNS', () => { it('reject burning if under slippage', async function () { // Get current curator name signal - const curatorNSignal = await gns.getCuratorNSignal( - me.address, - subgraphNumber0, - other.address, - ) + const curatorNSignal = await gns.getCuratorSignal(subgraph.id, other.address) // Withdraw - const { 1: expectedTokens } = await gns.nSignalToTokens( - me.address, - subgraphNumber0, - curatorNSignal, - ) + const { 1: expectedTokens } = await gns.nSignalToTokens(subgraph.id, curatorNSignal) // Force a revert by asking 1 more token than the function will return const tx = gns .connect(other.signer) - .burnNSignal(me.address, subgraphNumber0, curatorNSignal, expectedTokens.add(1)) + .burnSignal(subgraph.id, curatorNSignal, expectedTokens.add(1)) await expect(tx).revertedWith('Slippage protection') }) }) describe('withdraw()', async function () { + let subgraph: Subgraph + beforeEach(async () => { - await publishNewSubgraph(me, me.address, subgraphNumber0) - await mintNSignal(other, me.address, subgraphNumber0, tokens10000) + subgraph = await publishNewSubgraph(me, newSubgraph0) + await mintSignal(other, subgraph.id, tokens10000) }) it('should withdraw GRT from a disabled name signal', async function () { - await deprecateSubgraph(me, me.address, 0) - await withdraw(other, me.address, subgraphNumber0) + await deprecateSubgraph(me, subgraph.id) + await withdraw(other, subgraph.id) }) it('should fail if not disabled', async function () { - const tx = gns.connect(other.signer).withdraw(me.address, subgraphNumber0) - await expect(tx).revertedWith('GNS: Name bonding curve must be disabled first') + const tx = gns.connect(other.signer).withdraw(subgraph.id) + await expect(tx).revertedWith('GNS: Must be disabled first') }) it('should fail when there is no more GRT to withdraw', async function () { - await deprecateSubgraph(me, me.address, 0) - await withdraw(other, me.address, subgraphNumber0) - const tx = gns.connect(other.signer).withdraw(me.address, subgraphNumber0) + await deprecateSubgraph(me, subgraph.id) + await withdraw(other, subgraph.id) + const tx = gns.connect(other.signer).withdraw(subgraph.id) await expect(tx).revertedWith('GNS: No more GRT to withdraw') }) it('should fail if the curator has no nSignal', async function () { - await deprecateSubgraph(me, me.address, 0) - const tx = gns.connect(me.signer).withdraw(me.address, subgraphNumber0) - await expect(tx).revertedWith('GNS: Curator must have some nSignal to withdraw GRT') + await deprecateSubgraph(me, subgraph.id) + const tx = gns.connect(me.signer).withdraw(subgraph.id) + await expect(tx).revertedWith('GNS: No signal to withdraw GRT') }) }) @@ -882,24 +863,24 @@ describe('GNS', () => { toGRT('2000'), toGRT('123'), ] - await publishNewSubgraph(me, me.address, 0) + const subgraph = await publishNewSubgraph(me, newSubgraph0) // State updated const curationTaxPercentage = await curation.curationTaxPercentage() for (const tokensToDeposit of tokensToDepositMany) { - const poolOld = await gns.nameSignals(me.address, 0) - expect(subgraph0.subgraphDeploymentID).eq(poolOld.subgraphDeploymentID) + const beforeSubgraph = await gns.subgraphs(subgraph.id) + expect(newSubgraph0.subgraphDeploymentID).eq(beforeSubgraph.subgraphDeploymentID) const curationTax = toBN(curationTaxPercentage).mul(tokensToDeposit).div(toBN(1000000)) const expectedNSignal = await calcGNSBondingCurve( - poolOld.nSignal, - poolOld.vSignal, - poolOld.reserveRatio, + beforeSubgraph.nSignal, + beforeSubgraph.vSignal, + beforeSubgraph.reserveRatio, tokensToDeposit.sub(curationTax), - poolOld.subgraphDeploymentID, + beforeSubgraph.subgraphDeploymentID, ) - const tx = await mintNSignal(me, me.address, 0, tokensToDeposit) + const tx = await mintSignal(me, subgraph.id, tokensToDeposit) const receipt = await tx.wait() const event: Event = receipt.events.pop() const nSignalCreated = event.args['nSignalCreated'] @@ -924,33 +905,13 @@ describe('GNS', () => { toGRT('1'), // should mint below minimum deposit ] - await publishNewSubgraph(me, me.address, 0) + const subgraph = await publishNewSubgraph(me, newSubgraph0) // State updated for (const tokensToDeposit of tokensToDepositMany) { - await mintNSignal(me, me.address, 0, tokensToDeposit) + await mintSignal(me, subgraph.id, tokensToDeposit) } }) - - describe('setOwnerTaxPercentage', function () { - const newValue = 10 - - it('should set `ownerTaxPercentage`', async function () { - // Can set if allowed - await gns.connect(governor.signer).setOwnerTaxPercentage(newValue) - expect(await gns.ownerTaxPercentage()).eq(newValue) - }) - - it('reject set `ownerTaxPercentage` if out of bounds', async function () { - const tx = gns.connect(governor.signer).setOwnerTaxPercentage(1000001) - await expect(tx).revertedWith('Owner tax must be MAX_PPM or less') - }) - - it('reject set `ownerTaxPercentage` if not allowed', async function () { - const tx = gns.connect(me.signer).setOwnerTaxPercentage(newValue) - await expect(tx).revertedWith('Caller must be Controller governor') - }) - }) }) }) @@ -959,28 +920,14 @@ describe('GNS', () => { await curation.setMinimumCurationDeposit(toGRT('1')) // Publish a named subgraph-0 -> subgraphDeployment0 - await gns - .connect(me.signer) - .publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, - ) + const subgraph0 = await publishNewSubgraph(me, newSubgraph0) // Curate on the first subgraph - await gns.connect(me.signer).mintNSignal(me.address, 0, toGRT('90000'), 0) + await gns.connect(me.signer).mintSignal(subgraph0.id, toGRT('90000'), 0) // Publish a named subgraph-1 -> subgraphDeployment0 - await gns - .connect(me.signer) - .publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, - ) + const subgraph1 = await publishNewSubgraph(me, newSubgraph0) // Curate on the second subgraph should work - await gns.connect(me.signer).mintNSignal(me.address, 1, toGRT('10'), 0) + await gns.connect(me.signer).mintSignal(subgraph1.id, toGRT('10'), 0) }) }) @@ -988,19 +935,13 @@ describe('GNS', () => { it('should publish new subgraph and mint signal in single transaction', async function () { // Create a subgraph const tx1 = await gns.populateTransaction.publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, + newSubgraph0.subgraphDeploymentID, + newSubgraph0.versionMetadata, + newSubgraph0.subgraphMetadata, ) // Curate on the subgraph - const subgraphNumber = await gns.graphAccountSubgraphNumbers(me.address) - const tx2 = await gns.populateTransaction.mintNSignal( - me.address, - subgraphNumber, - toGRT('90000'), - 0, - ) + const subgraphID = buildSubgraphID(me.address, await gns.nextAccountSeqID(me.address)) + const tx2 = await gns.populateTransaction.mintSignal(subgraphID, toGRT('90000'), 0) // Batch send transaction await gns.connect(me.signer).multicall([tx1.data, tx2.data]) @@ -1012,10 +953,9 @@ describe('GNS', () => { // Create a subgraph const tx2 = await gns.populateTransaction.publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, + newSubgraph0.subgraphDeploymentID, + newSubgraph0.versionMetadata, + newSubgraph0.subgraphMetadata, ) // Batch send transaction @@ -1029,10 +969,9 @@ describe('GNS', () => { // Create a subgraph const tx2 = await gns.populateTransaction.publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, + newSubgraph0.subgraphDeploymentID, + newSubgraph0.versionMetadata, + newSubgraph0.subgraphMetadata, ) // Batch send transaction @@ -1051,10 +990,9 @@ describe('GNS', () => { // Create a subgraph const tx2 = await gns.populateTransaction.publishNewSubgraph( - me.address, - subgraph0.subgraphDeploymentID, - subgraph0.versionMetadata, - subgraph0.subgraphMetadata, + newSubgraph0.subgraphDeploymentID, + newSubgraph0.versionMetadata, + newSubgraph0.subgraphMetadata, ) // Batch send transaction diff --git a/test/lib/deployment.ts b/test/lib/deployment.ts index eb28b35dd..75b1b80df 100644 --- a/test/lib/deployment.ts +++ b/test/lib/deployment.ts @@ -176,14 +176,14 @@ export async function deployGNS( proxyAdmin: GraphProxyAdmin, ): Promise { // Dependency - const didRegistry = await deployEthereumDIDRegistry(deployer) const bondingCurve = (await deployContract('BancorFormula', deployer)) as unknown as BancorFormula + const subgraphDescriptor = await deployContract('SubgraphNFTDescriptor', deployer) // Deploy return network.deployContractWithProxy( proxyAdmin, 'GNS', - [controller, bondingCurve.address, didRegistry.address], + [controller, bondingCurve.address, subgraphDescriptor.address], deployer, ) as unknown as GNS } diff --git a/test/staking/delegation.test.ts b/test/staking/delegation.test.ts index 7755a1d57..dd7f32df5 100644 --- a/test/staking/delegation.test.ts +++ b/test/staking/delegation.test.ts @@ -594,6 +594,9 @@ describe('Staking::Delegation', () => { }) it('should send delegation cut of query fees to delegation pool', async function () { + // Use long enough epochs to avoid jumping to the next epoch involuntarily on our test + await epochManager.setEpochLength(toBN((60 * 60) / 15)) + // 1:10 delegation capacity await staking.connect(governor.signer).setDelegationRatio(10)