diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 86d86f251f..46f23c7bfd 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -559,6 +559,32 @@ type RoleRevokedEvent implements Event @entity { sender: Bytes! } +type SetEvent implements Event @entity { + id: ID! + transactionHash: Bytes! + gasPrice: BigInt! + timestamp: BigInt! + name: String! + + """ + Empty addresses array. + """ + addresses: [Bytes!]! + blockNumber: BigInt! + logIndex: BigInt! + order: BigInt! + + """ + Because the name property is indexed, the + returned value will be a keccak256 hash + of the string. + """ + hashedName: Bytes! + target: Bytes! + + resolverEntry: ResolverEntry! +} + # SuperfluidGovernance # type CFAv1LiquidationPeriodChangedEvent implements Event @entity { id: ID! @@ -1253,6 +1279,22 @@ type Token @entity { underlyingToken: Token } +type ResolverEntry @entity { + """ + ID: the keccak256 hash of the set name + """ + id: ID! + createdAtTimestamp: BigInt! + createdAtBlockNumber: BigInt! + updatedAtTimestamp: BigInt! + updatedAtBlockNumber: BigInt! + targetAddress: Bytes! + isToken: Boolean! + isListed: Boolean! + + setEvents: [SetEvent!]! @derivedFrom(field: "resolverEntry") +} + #################### # Aggr. Entities # #################### diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index 7636ae55d1..3b7e20563b 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -7,6 +7,7 @@ import { FlowOperator, Index, IndexSubscription, + ResolverEntry, Stream, StreamRevision, Token, @@ -103,7 +104,7 @@ export function getOrInitSuperToken( token.save(); - // Note: we initalize and create tokenStatistic whenever we create a + // Note: we initialize and create tokenStatistic whenever we create a // token as well. let tokenStatistic = getOrInitTokenStatistic(tokenAddress, block); tokenStatistic = updateTotalSupplyForNativeSuperToken( @@ -122,7 +123,6 @@ export function getOrInitSuperToken( let address = Address.fromString(underlyingAddress.toHexString()); getOrInitToken(address, block); } - return token as Token; } @@ -133,10 +133,12 @@ export function getOrInitSuperToken( token.save(); } - if (token.isListed == false) { - token = getIsListedToken(token as Token, tokenAddress, resolverAddress); - token.save(); - } + // @note - this is currently being called every single time to handle list/unlist of tokens + // because we don't have the Resolver Set event on some networks + // We can remove this once we have migrated data to a new resolver which emits this event on + // all networks. + token = getIsListedToken(token as Token, tokenAddress, resolverAddress); + token.save(); return token as Token; } @@ -372,6 +374,30 @@ export function getOrInitSubscription( return subscription as IndexSubscription; } +export function getOrInitResolverEntry( + id: string, + target: Address, + block: ethereum.Block +): ResolverEntry { + let resolverEntry = ResolverEntry.load(id); + + if (resolverEntry == null) { + resolverEntry = new ResolverEntry(id); + resolverEntry.createdAtBlockNumber = block.number; + resolverEntry.createdAtTimestamp = block.timestamp; + resolverEntry.targetAddress = target; + + const superToken = Token.load(target.toHex()); + resolverEntry.isToken = superToken != null; + } + resolverEntry.updatedAtBlockNumber = block.number; + resolverEntry.updatedAtTimestamp = block.timestamp; + resolverEntry.isListed = target.notEqual(ZERO_ADDRESS); + + resolverEntry.save(); + return resolverEntry as ResolverEntry; +} + /************************************************************************** * Aggregate initializer functions *************************************************************************/ diff --git a/packages/subgraph/src/mappings/resolver.ts b/packages/subgraph/src/mappings/resolver.ts index 192042ed09..52b8e26f4e 100644 --- a/packages/subgraph/src/mappings/resolver.ts +++ b/packages/subgraph/src/mappings/resolver.ts @@ -1,14 +1,19 @@ +import { Address, Bytes, ethereum } from "@graphprotocol/graph-ts"; import { RoleAdminChanged, RoleGranted, RoleRevoked, + Set, } from "../../generated/ResolverV1/Resolver"; import { RoleAdminChangedEvent, RoleGrantedEvent, RoleRevokedEvent, + SetEvent, + Token, } from "../../generated/schema"; -import { createEventID, getOrder } from "../utils"; +import { getOrInitResolverEntry } from "../mappingHelpers"; +import { createEventID, getOrder, ZERO_ADDRESS } from "../utils"; export function handleRoleAdminChanged(event: RoleAdminChanged): void { let ev = new RoleAdminChangedEvent( @@ -58,3 +63,46 @@ export function handleRoleRevoked(event: RoleRevoked): void { ev.sender = event.params.sender; ev.save(); } + +export function handleSet(event: Set): void { + _createSetEvent(event, event.params.target, event.params.name); + + const resolverEntry = getOrInitResolverEntry( + event.params.name.toHex(), + event.params.target, + event.block + ); + + if (resolverEntry.isToken) { + const token = Token.load(resolverEntry.targetAddress.toHex()); + if (token) { + if (event.params.target.equals(ZERO_ADDRESS)) { + token.isListed = false; + } else { + token.isListed = true; + } + token.save(); + } + } +} + +function _createSetEvent( + event: ethereum.Event, + target: Bytes, + name: Bytes +): void { + const ev = new SetEvent(createEventID("Set", event)); + ev.transactionHash = event.transaction.hash; + ev.gasPrice = event.transaction.gasPrice; + ev.timestamp = event.block.timestamp; + ev.blockNumber = event.block.number; + ev.logIndex = event.logIndex; + ev.order = getOrder(event.block.number, event.logIndex); + ev.name = "Set"; + ev.addresses = [target]; + + ev.hashedName = name; + ev.target = target; + ev.resolverEntry = name.toHex(); + ev.save(); +} diff --git a/packages/subgraph/subgraph.template.yaml b/packages/subgraph/subgraph.template.yaml index f1bd8f67ba..548ab4c801 100644 --- a/packages/subgraph/subgraph.template.yaml +++ b/packages/subgraph/subgraph.template.yaml @@ -13,7 +13,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superTokenFactory.ts entities: @@ -47,7 +47,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/host.ts entities: @@ -86,7 +86,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/cfav1.ts entities: @@ -124,7 +124,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/idav1.ts entities: @@ -183,13 +183,16 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/resolver.ts entities: - RoleAdminChangedEvent - RoleGrantedEvent - RoleRevokedEvent + - ResolverEntry + - SetEvent + - SuperToken abis: - name: Resolver file: ./abis/Resolver.json @@ -200,6 +203,8 @@ dataSources: handler: handleRoleGranted - event: RoleRevoked(indexed bytes32,indexed address,indexed address) handler: handleRoleRevoked + - event: Set(indexed string,address) + handler: handleSet templates: - name: SuperToken kind: ethereum/contract @@ -208,7 +213,7 @@ templates: abi: ISuperToken mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superToken.ts entities: @@ -255,7 +260,7 @@ templates: abi: SuperfluidGovernanceBase mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superfluidGovernance.ts entities: diff --git a/packages/subgraph/tasks/testenv-ctl.sh b/packages/subgraph/tasks/testenv-ctl.sh index 91799fa521..17d2b1b996 100755 --- a/packages/subgraph/tasks/testenv-ctl.sh +++ b/packages/subgraph/tasks/testenv-ctl.sh @@ -10,7 +10,9 @@ if [ "$CMD" == "start" ];then cd ../ethereum-contracts # Install contract dependencies and build contracts yarn install - yarn run build:contracts + if [ "$BUILT" == "" ];then + yarn run build:contracts + fi # Get ABIs and generate typechain in subgraph folder based on ABIs cd ../subgraph yarn getAbi diff --git a/packages/subgraph/test-subgraph.template.yaml b/packages/subgraph/test-subgraph.template.yaml index 09158dd035..5b66c78ce5 100644 --- a/packages/subgraph/test-subgraph.template.yaml +++ b/packages/subgraph/test-subgraph.template.yaml @@ -13,7 +13,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superTokenFactory.ts entities: @@ -47,7 +47,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/host.ts entities: @@ -86,7 +86,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/cfav1.ts entities: @@ -124,7 +124,7 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/idav1.ts entities: @@ -183,13 +183,16 @@ dataSources: startBlock: {{ hostStartBlock }} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/resolver.ts entities: - RoleAdminChangedEvent - RoleGrantedEvent - RoleRevokedEvent + - ResolverEntry + - SetEvent + - SuperToken abis: - name: Resolver file: ./abis/Resolver.json @@ -200,6 +203,8 @@ dataSources: handler: handleRoleGranted - event: RoleRevoked(indexed bytes32,indexed address,indexed address) handler: handleRoleRevoked + - event: Set(indexed string,address) + handler: handleSet templates: - name: SuperToken kind: ethereum/contract @@ -208,7 +213,7 @@ templates: abi: ISuperToken mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superToken.ts entities: @@ -255,7 +260,7 @@ templates: abi: SuperfluidGovernanceBase mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.6 language: wasm/assemblyscript file: ./src/mappings/superfluidGovernance.ts entities: diff --git a/packages/subgraph/test/helpers/helpers.ts b/packages/subgraph/test/helpers/helpers.ts index 8b23d4fb32..5488c23b4b 100644 --- a/packages/subgraph/test/helpers/helpers.ts +++ b/packages/subgraph/test/helpers/helpers.ts @@ -19,7 +19,9 @@ import {BigInt} from "@graphprotocol/graph-ts"; // the resolver address should be consistent as long as you use the // first account retrieved by hardhat's ethers.getSigners(): // 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 and the nonce is 0 -const RESOLVER_ADDRESS = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; +const RESOLVER_ADDRESS = + process.env.RESOLVER_ADDRESS || + "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; const ORDER_MULTIPLIER = 10000; // This number is also defined as ORDER_MULTIPLIER in packages/subgraph/src/utils.ts const MAX_SAFE_SECONDS = BigNumber.from("8640000000000") // This number is also defined as MAX_SAFE_SECONDS in packages/subgraph/src/utils.ts /************************************************************************** diff --git a/packages/subgraph/test/subgraph.test.ts b/packages/subgraph/test/subgraph.test.ts index ce3da3f269..effb932104 100644 --- a/packages/subgraph/test/subgraph.test.ts +++ b/packages/subgraph/test/subgraph.test.ts @@ -1,5 +1,5 @@ import { ethers } from "hardhat"; -import { Framework, SuperToken } from "@superfluid-finance/sdk-core"; +import { Framework, SFError, SuperToken } from "@superfluid-finance/sdk-core"; import { TestToken } from "../typechain"; import { asleep, @@ -10,6 +10,7 @@ import { monthlyToSecondRate, subgraphRequest, toBN, + waitUntilBlockIndexed, } from "./helpers/helpers"; import { IAccountTokenSnapshot, @@ -255,6 +256,42 @@ describe("Subgraph Tests", () => { 18 ); }); + + it("Should be able to unlist and relist tokens", async () => { + const [deployer] = await ethers.getSigners(); + + // MUST WAIT until block indexed each time to catch up + // Unlist fDAIx + let txn = await framework.contracts.resolver + .connect(deployer) + .set("supertokens.test.fDAIx", ethers.constants.AddressZero); + + let receipt = await txn.wait(); + await waitUntilBlockIndexed(receipt.blockNumber); + await fetchTokenAndValidate( + daix.address.toLowerCase(), + "Super fDAI Fake Token", + "fDAIx", + false, + dai.address, + 18 + ); + + // List fDAIx + txn = await framework.contracts.resolver + .connect(deployer) + .set("supertokens.test.fDAIx", daix.address); + receipt = await txn.wait(); + await waitUntilBlockIndexed(receipt.blockNumber); + await fetchTokenAndValidate( + daix.address.toLowerCase(), + "Super fDAI Fake Token", + "fDAIx", + true, + dai.address, + 18 + ); + }); }); describe("ConstantFlowAgreementV1 Tests", () => {