From 24a549c29d7fd0db14d496bbb03ed51f3751e7c5 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Tue, 26 Mar 2024 13:50:37 +0200 Subject: [PATCH] [subgraph] upcoming feature set for the subgraph (#1889) * [SUBGRAPH] [BUG] PoolMember not getting updated when the member units change (#1877) * add a test starter * add the test * fixes * improve test * update dev container for matchstick compatibility * implement the fix * clean-up * add elaborate test for pool total amount received * fix test issue * add even more comments * fix test name * ignore test --------- Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> * [SUBGRAPH] approval mapping (#1878) * approval mapping * missing addresses * missing addresses cont. * no-op, trigger build * fix tests --------- Co-authored-by: Kaspar Kallas * [subgraph] Fix Correctness of GDA Entities (#1890) --------- Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> * add createdAtTimestamp/BlockNumber for ATS entity (#1909) * [SUBGRAPH] fix governance config always null (#1908) * fix governance config always null * fix broken tests * [SUBGRAPH] fix resolver entry (#1843) * fix resolver entry * do resolver rpc call on token initialization and remove handleTokenRPCCalls in subsequent token calls * fix broken tests * TODO comment can be removed * remove mistakenly added files --------- Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> --- .devcontainer/devcontainer.json | 7 +- packages/subgraph/schema.graphql | 109 +++- packages/subgraph/src/addresses.template.ts | 4 + packages/subgraph/src/mappingHelpers.ts | 199 +++++--- packages/subgraph/src/mappings/flowNFT.ts | 8 +- packages/subgraph/src/mappings/gdav1.ts | 30 +- packages/subgraph/src/mappings/host.ts | 14 +- packages/subgraph/src/mappings/resolver.ts | 53 +- packages/subgraph/src/mappings/superToken.ts | 12 + .../src/mappings/superTokenFactory.ts | 2 +- .../src/mappings/superfluidGovernance.ts | 17 +- .../subgraph/src/mappings/superfluidPool.ts | 67 ++- packages/subgraph/src/utils.ts | 32 +- .../2024-02-29-aleph-total-supply.test.ts | 4 +- ...24-03-06-pool-member-units-changed.test.ts | 89 ++++ ...-pool-member-total-amount-received.test.ts | 481 ++++++++++++++++++ packages/subgraph/tests/constants.ts | 2 +- .../tests/gdav1/hol/gdav1.hol.test.ts | 10 +- .../tests/idav1/event/idav1.event.test.ts | 6 +- packages/subgraph/tests/mockedFunctions.ts | 27 +- .../superToken/event/superToken.event.test.ts | 30 +- .../tests/superToken/superToken.helper.ts | 14 + 22 files changed, 1022 insertions(+), 195 deletions(-) create mode 100644 packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts create mode 100644 packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a6cff952a4..dcfd2038eb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Node.js & TypeScript", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", // Features to add to the dev container. More info: https://containers.dev/features. "features": { // If having issues with Nix then consult: @@ -19,7 +19,9 @@ "ghcr.io/lukewiwa/features/shellcheck:0": {}, "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, - "ghcr.io/devcontainers-contrib/features/act:1": {} + "ghcr.io/devcontainers-contrib/features/act:1": {}, + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {} }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], @@ -32,6 +34,7 @@ "source /home/node/.bashrc && foundryup", "yarn global add npm-run-all", "yarn install && yarn build", + "sudo apt-get install libpq5", // for subgraph's matchstick "./tasks/fix-devcontainer.sh && nix develop . -c bash <(echo \"yarn install && yarn build\")" ] // Configure tool-specific properties. diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 1627c06aa4..d66a1060da 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -830,7 +830,8 @@ type AgreementClassRegisteredEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `code` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -849,7 +850,8 @@ type AgreementClassUpdatedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `code` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -868,7 +870,8 @@ type AppRegisteredEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `app` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -889,7 +892,9 @@ type GovernanceReplacedEvent implements Event @entity(immutable: true) { order: BigInt! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `oldGovernance` + addresses[1] = `newGovernance` """ addresses: [Bytes!]! oldGovernance: Bytes! @@ -905,7 +910,8 @@ type JailEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `app` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -924,7 +930,8 @@ type SuperTokenFactoryUpdatedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `newFactory` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -942,7 +949,9 @@ type SuperTokenLogicUpdatedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `token` + addresses[1] = `code` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -963,7 +972,9 @@ type RoleAdminChangedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `previousAdminRole` + addresses[1] = `newAdminRole` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -983,7 +994,9 @@ type RoleGrantedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `account` + addresses[1] = `sender` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1003,7 +1016,9 @@ type RoleRevokedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `account` + addresses[1] = `sender` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1057,7 +1072,10 @@ type CFAv1LiquidationPeriodChangedEvent implements Event governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1083,7 +1101,10 @@ type ConfigChangedEvent implements Event @entity(immutable: true) { governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1110,7 +1131,11 @@ type RewardAddressChangedEvent implements Event @entity(immutable: true) { governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` + addresses[3] = `rewardAddress` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1136,7 +1161,10 @@ type PPPConfigurationChangedEvent implements Event @entity(immutable: true) { governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1164,7 +1192,10 @@ type SuperTokenMinimumDepositChangedEvent implements Event governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1190,7 +1221,11 @@ type TrustedForwarderChangedEvent implements Event @entity(immutable: true) { governanceAddress: Bytes! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `governanceAddress` + addresses[1] = `host` + addresses[2] = `superToken` + addresses[3] = `forwarder` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1468,20 +1503,39 @@ type ApprovalEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `isNFTApproval` ? `nft address` : `token` (superToken) + addresses[1] = `owner` + addresses[2] = `to` """ addresses: [Bytes!]! blockNumber: BigInt! logIndex: BigInt! order: BigInt! + """ + The address that will be granting allowance to transfer ERC20/NFT. + """ owner: Account! """ - The address that will be granted allowance to transfer the NFT. + The address that will be granted allowance to transfer ERC20/NFT. """ to: Account! + + """ + Indicates whether the event was emitted for the approval of an NFT. """ + isNFTApproval: Boolean! + + """ + If `amount` is non-zero, this event was emitted for the approval of an ERC20. + Tne amount of ERC20 tokens that will be granted allowance to transfer. + """ + amount: BigInt! + + """ + If `tokenId` is non-zero, this event was emitted for the approval of an NFT. The id of the NFT that will be granted allowance to transfer. The id is: uint256(keccak256(abi.encode(block.chainid, superToken, sender, receiver))) """ @@ -1497,7 +1551,10 @@ type ApprovalForAllEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = NFT address + addresses[1] = `owner` + addresses[2] = `operator` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1590,7 +1647,8 @@ type SuperTokenLogicCreatedEvent implements Event @entity(immutable: true) { name: String! """ - Empty addresses array. + Contains the addresses that were impacted by this event: + addresses[0] = `tokenLogic` """ addresses: [Bytes!]! blockNumber: BigInt! @@ -1757,6 +1815,11 @@ type Pool @entity { totalAmountInstantlyDistributedUntilUpdatedAt: BigInt! totalAmountFlowedDistributedUntilUpdatedAt: BigInt! totalAmountDistributedUntilUpdatedAt: BigInt! + totalFlowAdjustmentAmountDistributedUntilUpdatedAt: BigInt! + + perUnitSettledValue: BigInt! + perUnitFlowRate: BigInt! + """ A member is any account which has more than 0 units in the pool. """ @@ -1812,6 +1875,9 @@ type PoolMember @entity { poolTotalAmountDistributedUntilUpdatedAt: BigInt! totalAmountReceivedUntilUpdatedAt: BigInt! + syncedPerUnitSettledValue: BigInt! + syncedPerUnitFlowRate: BigInt! + account: Account! pool: Pool! @@ -2206,6 +2272,7 @@ type Token @entity { underlyingToken: Token """ + If `governanceConfig.id` is the zero address, the token uses the default governance config. """ governanceConfig: TokenGovernanceConfig } @@ -2237,6 +2304,8 @@ type AccountTokenSnapshot @entity { ID composed of: accountID-tokenID """ id: ID! + createdAtTimestamp: BigInt! + createdAtBlockNumber: BigInt! updatedAtTimestamp: BigInt! updatedAtBlockNumber: BigInt! # ---------------------------------- state ---------------------------------- diff --git a/packages/subgraph/src/addresses.template.ts b/packages/subgraph/src/addresses.template.ts index ad6559454c..4edf31d65b 100644 --- a/packages/subgraph/src/addresses.template.ts +++ b/packages/subgraph/src/addresses.template.ts @@ -15,3 +15,7 @@ export function getResolverAddress(): Address { export function getNativeAssetSuperTokenAddress(): Address { return Address.fromString("{{nativeAssetSuperTokenAddress}}"); } + +export function getIsLocalIntegrationTesting(): boolean { + return "{{testNetwork}}".length > 0; +} \ No newline at end of file diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index a272e5d05b..261e168f14 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -1,4 +1,5 @@ import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { FlowUpdated } from "../generated/ConstantFlowAgreementV1/IConstantFlowAgreementV1"; import { ISuperfluid as Superfluid } from "../generated/Host/ISuperfluid"; import { Account, @@ -10,7 +11,6 @@ import { Pool, PoolDistributor, PoolMember, - ResolverEntry, Stream, StreamRevision, Token, @@ -18,34 +18,33 @@ import { TokenStatistic, TokenStatisticLog, } from "../generated/schema"; +import { SuperToken as SuperTokenTemplate } from "../generated/templates"; +import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; +import { + getHostAddress, + getNativeAssetSuperTokenAddress, + getResolverAddress, +} from "./addresses"; import { BIG_INT_ZERO, - createLogID, + ZERO_ADDRESS, calculateMaybeCriticalAtTimestamp, + createLogID, getAccountTokenSnapshotID, + getActiveStreamsDelta, getAmountStreamedSinceLastUpdatedAt, + getClosedStreamsDelta, getFlowOperatorID, getIndexID, + getIsTokenListed, getOrder, + getPoolDistributorID, + getPoolMemberID, getStreamID, getStreamRevisionID, getSubscriptionID, - ZERO_ADDRESS, - handleTokenRPCCalls, - getPoolMemberID, - getPoolDistributorID, - getActiveStreamsDelta, - getClosedStreamsDelta, - MAX_UINT256, + handleTokenRPCCalls } from "./utils"; -import { SuperToken as SuperTokenTemplate } from "../generated/templates"; -import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; -import { - getHostAddress, - getNativeAssetSuperTokenAddress, - getResolverAddress, -} from "./addresses"; -import { FlowUpdated } from "../generated/ConstantFlowAgreementV1/IConstantFlowAgreementV1"; /************************************************************************** * HOL initializer functions @@ -118,10 +117,11 @@ export function getOrInitSuperToken( nativeAssetSuperTokenAddress ); - token = handleTokenRPCCalls(token, resolverAddress); - token.isListed = false; + token = handleTokenRPCCalls(token); + token.isListed = getIsTokenListed(token, resolverAddress); const underlyingAddress = token.underlyingAddress; token.underlyingToken = underlyingAddress.toHexString(); + token.governanceConfig = ZERO_ADDRESS.toHexString(); token.save(); @@ -148,12 +148,6 @@ export function getOrInitSuperToken( return token as Token; } - // @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 = handleTokenRPCCalls(token, resolverAddress); - token.save(); return token as Token; @@ -204,6 +198,12 @@ export function getOrInitTokenGovernanceConfig( governanceConfig.token = superTokenAddress.toHexString(); governanceConfig.save(); + + const superToken = Token.load(superTokenAddress.toHexString()); + if (superToken) { + superToken.governanceConfig = governanceConfig.id; + superToken.save(); + } } return governanceConfig; } @@ -234,7 +234,7 @@ export function getOrInitToken( token.isNativeAssetSuperToken = false; token.isListed = false; - token = handleTokenRPCCalls(token, resolverAddress); + token = handleTokenRPCCalls(token); token.save(); } @@ -457,30 +457,6 @@ 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; -} - export function getOrInitPool(event: ethereum.Event, poolId: string): Pool { // get existing pool let pool = Pool.load(poolId); @@ -499,6 +475,11 @@ export function getOrInitPool(event: ethereum.Event, poolId: string): Pool { pool.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO; pool.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO; pool.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; + pool.totalFlowAdjustmentAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; + + pool.perUnitSettledValue = BIG_INT_ZERO; + pool.perUnitFlowRate = BIG_INT_ZERO; + pool.totalMembers = 0; pool.totalConnectedMembers = 0; pool.totalDisconnectedMembers = 0; @@ -519,9 +500,6 @@ export function updatePoolTotalAmountFlowedAndDistributed( const timeDelta = event.block.timestamp.minus(pool.updatedAtTimestamp); const amountFlowedSinceLastUpdate = pool.flowRate.times(timeDelta); - pool.updatedAtBlockNumber = event.block.number; - pool.updatedAtTimestamp = event.block.timestamp; - pool.totalAmountFlowedDistributedUntilUpdatedAt = pool.totalAmountFlowedDistributedUntilUpdatedAt.plus( amountFlowedSinceLastUpdate @@ -536,7 +514,7 @@ export function updatePoolTotalAmountFlowedAndDistributed( return pool; } -export function getOrInitPoolMember( +export function getOrInitOrUpdatePoolMember( event: ethereum.Event, poolAddress: Address, poolMemberAddress: Address @@ -548,19 +526,25 @@ export function getOrInitPoolMember( poolMember = new PoolMember(poolMemberID); poolMember.createdAtTimestamp = event.block.timestamp; poolMember.createdAtBlockNumber = event.block.number; - poolMember.updatedAtTimestamp = event.block.timestamp; - poolMember.updatedAtBlockNumber = event.block.number; - + poolMember.units = BIG_INT_ZERO; poolMember.isConnected = false; poolMember.totalAmountClaimed = BIG_INT_ZERO; poolMember.poolTotalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; poolMember.totalAmountReceivedUntilUpdatedAt = BIG_INT_ZERO; + poolMember.syncedPerUnitSettledValue = BIG_INT_ZERO; + poolMember.syncedPerUnitFlowRate = BIG_INT_ZERO; + poolMember.account = poolMemberAddress.toHex(); poolMember.pool = poolAddress.toHex(); } - + poolMember.updatedAtTimestamp = event.block.timestamp; + poolMember.updatedAtBlockNumber = event.block.number; + + poolMember.updatedAtTimestamp = event.block.timestamp; + poolMember.updatedAtBlockNumber = event.block.number; + return poolMember; } @@ -636,6 +620,8 @@ export function getOrInitAccountTokenSnapshot( if (accountTokenSnapshot == null) { accountTokenSnapshot = new AccountTokenSnapshot(atsId); + accountTokenSnapshot.createdAtTimestamp = block.timestamp; + accountTokenSnapshot.createdAtBlockNumber = block.number; accountTokenSnapshot.updatedAtTimestamp = block.timestamp; accountTokenSnapshot.updatedAtBlockNumber = block.number; accountTokenSnapshot.totalNumberOfActiveStreams = 0; @@ -1471,33 +1457,80 @@ export function updateAggregateEntitiesTransferData( tokenStatistic.save(); } + +export function particleRTB( + perUnitSettledValue: BigInt, + perUnitFlowRate: BigInt, + currentTimestamp: BigInt, + lastUpdatedTimestamp: BigInt +): BigInt { + const amountFlowedPerUnit = perUnitFlowRate.times(currentTimestamp.minus(lastUpdatedTimestamp)); + return perUnitSettledValue.plus(amountFlowedPerUnit); +} + +export function monetaryUnitPoolMemberRTB(pool: Pool, poolMember: PoolMember, currentTimestamp: BigInt): BigInt { + const poolPerUnitRTB = particleRTB( + pool.perUnitSettledValue, + pool.perUnitFlowRate, + currentTimestamp, + pool.updatedAtTimestamp + ); + const poolMemberPerUnitRTB = particleRTB( + poolMember.syncedPerUnitSettledValue, + poolMember.syncedPerUnitFlowRate, + currentTimestamp, + poolMember.updatedAtTimestamp + ); + + return poolMember.totalAmountReceivedUntilUpdatedAt.plus( + poolPerUnitRTB.minus(poolMemberPerUnitRTB).times(poolMember.units) + ); +} + /** - * Updates `totalAmountReceivedUntilUpdatedAt` and `poolTotalAmountDistributedUntilUpdatedAt` fields - * Requires an explicit save on the PoolMember entity. - * Requires `pool.totalAmountDistributedUntilUpdatedAt` is updated *BEFORE* this function is called. - * Requires that pool.totalUnits and poolMember.units are updated *AFTER* this function is called. - * @param pool the pool entity - * @param poolMember the pool member entity - * @returns the updated pool member entity to be saved + * Updates the pool.perUnitSettledValue to the up to date value based on the current block, + * and updates the updatedAtTimestamp and updatedAtBlockNumber. + * @param pool pool entity + * @param block current block + * @returns updated pool entity */ -export function updatePoolMemberTotalAmountUntilUpdatedAtFields(pool: Pool, poolMember: PoolMember): PoolMember { - let amountReceivedDelta = BIG_INT_ZERO; - // if the pool member has any units, we calculate the delta - // otherwise the delta is going to be 0 - if (!poolMember.units.equals(BIG_INT_ZERO)) { - const distributedAmountDelta = pool.totalAmountDistributedUntilUpdatedAt - .minus(poolMember.poolTotalAmountDistributedUntilUpdatedAt); - - const isSafeToMultiplyWithoutOverflow = MAX_UINT256.div(poolMember.units).gt(distributedAmountDelta); - if (isSafeToMultiplyWithoutOverflow) { - amountReceivedDelta = distributedAmountDelta.times(poolMember.units).div(pool.totalUnits); - } else { - amountReceivedDelta = distributedAmountDelta.div(pool.totalUnits).times(poolMember.units); - } - } - poolMember.totalAmountReceivedUntilUpdatedAt = - poolMember.totalAmountReceivedUntilUpdatedAt.plus(amountReceivedDelta); - poolMember.poolTotalAmountDistributedUntilUpdatedAt = pool.totalAmountDistributedUntilUpdatedAt; +export function settlePoolParticle(pool: Pool, block: ethereum.Block): Pool { + pool.perUnitSettledValue = particleRTB( + pool.perUnitSettledValue, + pool.perUnitFlowRate, + block.timestamp, + pool.updatedAtTimestamp + ); + pool.updatedAtTimestamp = block.timestamp; + pool.updatedAtBlockNumber = block.number; + + return pool; +} + +export function settlePoolMemberParticle(poolMember: PoolMember, block: ethereum.Block): PoolMember { + poolMember.syncedPerUnitSettledValue = particleRTB( + poolMember.syncedPerUnitSettledValue, + poolMember.syncedPerUnitFlowRate, + block.timestamp, + poolMember.updatedAtTimestamp + ); + poolMember.updatedAtTimestamp = block.timestamp; + poolMember.updatedAtBlockNumber = block.number; + + return poolMember; +} + +export function syncPoolMemberParticle(pool: Pool, poolMember: PoolMember): PoolMember { + poolMember.syncedPerUnitSettledValue = pool.perUnitSettledValue; + poolMember.syncedPerUnitFlowRate = pool.perUnitFlowRate; + poolMember.updatedAtTimestamp = pool.updatedAtTimestamp; + poolMember.updatedAtBlockNumber = pool.updatedAtBlockNumber; return poolMember; } + +export function settlePDPoolMemberMU(pool: Pool, poolMember: PoolMember, block: ethereum.Block): void { + pool = settlePoolParticle(pool, block); + poolMember.totalAmountReceivedUntilUpdatedAt = monetaryUnitPoolMemberRTB(pool, poolMember, block.timestamp); + poolMember = syncPoolMemberParticle(pool, poolMember); +} diff --git a/packages/subgraph/src/mappings/flowNFT.ts b/packages/subgraph/src/mappings/flowNFT.ts index a070abf8dd..2124988e1e 100644 --- a/packages/subgraph/src/mappings/flowNFT.ts +++ b/packages/subgraph/src/mappings/flowNFT.ts @@ -10,15 +10,17 @@ import { MetadataUpdateEvent, TransferEvent, } from "../../generated/schema"; -import { createEventID, initializeEventEntity } from "../utils"; +import { BIG_INT_ONE, createEventID, initializeEventEntity } from "../utils"; export function handleApproval(event: Approval): void { const eventId = createEventID("Approval", event); const ev = new ApprovalEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.owner, event.params.approved]); ev.owner = event.params.owner.toHex(); ev.to = event.params.approved.toHex(); ev.tokenId = event.params.tokenId; + ev.amount = BIG_INT_ONE.neg(); + ev.isNFTApproval = true; ev.save(); } @@ -26,7 +28,7 @@ export function handleApproval(event: Approval): void { export function handleApprovalForAll(event: ApprovalForAll): void { const eventId = createEventID("ApprovalForAll", event); const ev = new ApprovalForAllEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.owner, event.params.operator]); ev.owner = event.params.owner.toHex(); ev.operator = event.params.operator.toHex(); ev.approved = event.params.approved; diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index 81381e7464..19762416f7 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -19,12 +19,13 @@ import { _createTokenStatisticLogEntity, getOrInitPool, getOrInitPoolDistributor, - getOrInitPoolMember, + getOrInitOrUpdatePoolMember, getOrInitTokenStatistic, + settlePDPoolMemberMU, + settlePoolParticle, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, updatePoolDistributorTotalAmountFlowedAndDistributed, - updatePoolMemberTotalAmountUntilUpdatedAtFields, updatePoolTotalAmountFlowedAndDistributed, updateSenderATSStreamData, updateTokenStatisticStreamData, @@ -33,6 +34,7 @@ import { import { BIG_INT_ZERO, createEventID, + divideOrZero, initializeEventEntity, membershipWithUnitsExists, } from "../utils"; @@ -84,7 +86,7 @@ export function handlePoolConnectionUpdated( event: PoolConnectionUpdated ): void { // Update Pool Member Entity - let poolMember = getOrInitPoolMember( + let poolMember = getOrInitOrUpdatePoolMember( event, event.params.pool, event.params.account @@ -98,7 +100,9 @@ export function handlePoolConnectionUpdated( // Update Pool Entity let pool = getOrInitPool(event, event.params.pool.toHex()); + // @note we modify pool and poolMember here in memory, but do not save pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); if (poolMember.units.gt(BIG_INT_ZERO)) { if (memberConnectedStatusUpdated) { // disconnected -> connected case @@ -126,9 +130,6 @@ export function handlePoolConnectionUpdated( } } } - - // Update totalAmountDistributedUntilUpdatedAt - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); pool.save(); poolMember.save(); @@ -162,6 +163,9 @@ export function handlePoolConnectionUpdated( false // isIDA ); + // Create Event Entity + _createPoolConnectionUpdatedEntity(event, poolMember.id); + // Create ATS and Token Statistic Log Entities const eventName = "PoolConnectionUpdated"; _createAccountTokenSnapshotLogEntity( @@ -172,9 +176,6 @@ export function handlePoolConnectionUpdated( ); _createTokenStatisticLogEntity(event, event.params.token, eventName); - - // Create Event Entity - _createPoolConnectionUpdatedEntity(event, poolMember.id); } export function handleBufferAdjusted(event: BufferAdjusted): void { @@ -229,7 +230,12 @@ export function handleFlowDistributionUpdated( // Update Pool let pool = getOrInitPool(event, event.params.pool.toHex()); + + // @note that we are duplicating update of updatedAtTimestamp/BlockNumber here + // in the two functions pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + pool = settlePoolParticle(pool, event.block); + pool.perUnitFlowRate = divideOrZero(event.params.newDistributorToPoolFlowRate, pool.totalUnits); pool.flowRate = event.params.newTotalDistributionFlowRate; pool.adjustmentFlowRate = event.params.adjustmentFlowRate; pool.save(); @@ -312,7 +318,13 @@ export function handleInstantDistributionUpdated( // Update Pool let pool = getOrInitPool(event, event.params.pool.toHex()); + + // @note that we are duplicating update of updatedAtTimestamp/BlockNumber here + // in the two functions pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + pool = settlePoolParticle(pool, event.block); + // @note a speculations on what needs to be done + pool.perUnitSettledValue = pool.perUnitSettledValue.plus(divideOrZero(event.params.actualAmount, pool.totalUnits)); const previousTotalAmountDistributed = pool.totalAmountDistributedUntilUpdatedAt; pool.totalAmountInstantlyDistributedUntilUpdatedAt = diff --git a/packages/subgraph/src/mappings/host.ts b/packages/subgraph/src/mappings/host.ts index 7b877d3ff4..738afd49d0 100644 --- a/packages/subgraph/src/mappings/host.ts +++ b/packages/subgraph/src/mappings/host.ts @@ -25,7 +25,7 @@ import { SuperfluidGovernance } from "../../generated/templates"; export function handleGovernanceReplaced(event: GovernanceReplaced): void { const eventId = createEventID("GovernanceReplaced", event); const ev = new GovernanceReplacedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.oldGov, event.params.newGov]); ev.oldGovernance = event.params.oldGov; ev.newGovernance = event.params.newGov; ev.save(); @@ -42,7 +42,7 @@ export function handleAgreementClassRegistered( ): void { const eventId = createEventID("AgreementClassRegistered", event); const ev = new AgreementClassRegisteredEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.code]); ev.agreementType = event.params.agreementType; ev.code = event.params.code; ev.save(); @@ -55,7 +55,7 @@ export function handleAgreementClassUpdated( ): void { const eventId = createEventID("AgreementClassUpdated", event); const ev = new AgreementClassUpdatedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.code]); ev.agreementType = event.params.agreementType; ev.code = event.params.code; @@ -70,7 +70,7 @@ export function handleSuperTokenFactoryUpdated( ): void { const eventId = createEventID("SuperTokenFactoryUpdated", event); const ev = new SuperTokenFactoryUpdatedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.newFactory]); ev.newFactory = event.params.newFactory; ev.save(); @@ -81,7 +81,7 @@ export function handleSuperTokenLogicUpdated( ): void { const eventId = createEventID("SuperTokenLogicUpdated", event); const ev = new SuperTokenLogicUpdatedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.token, event.params.code]); ev.token = event.params.token; ev.code = event.params.code; @@ -90,7 +90,7 @@ export function handleSuperTokenLogicUpdated( export function handleAppRegistered(event: AppRegistered): void { const ev = new AppRegisteredEvent(createEventID("AppRegistered", event)); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.app]); ev.app = event.params.app; ev.save(); @@ -98,7 +98,7 @@ export function handleAppRegistered(event: AppRegistered): void { export function handleJail(event: Jail): void { const ev = new JailEvent(createEventID("Jail", event)); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.app]); ev.app = event.params.app; ev.reason = event.params.reason; diff --git a/packages/subgraph/src/mappings/resolver.ts b/packages/subgraph/src/mappings/resolver.ts index 69a0d3dc26..b66a68290a 100644 --- a/packages/subgraph/src/mappings/resolver.ts +++ b/packages/subgraph/src/mappings/resolver.ts @@ -1,24 +1,19 @@ -import { Bytes, ethereum } from "@graphprotocol/graph-ts"; -import { - RoleAdminChanged, - RoleGranted, - RoleRevoked, - Set, -} from "../../generated/ResolverV1/Resolver"; +import { Address, Bytes, ethereum } from "@graphprotocol/graph-ts"; +import { RoleAdminChanged, RoleGranted, RoleRevoked, Set } from "../../generated/ResolverV1/Resolver"; import { + ResolverEntry, RoleAdminChangedEvent, RoleGrantedEvent, RoleRevokedEvent, SetEvent, Token, } from "../../generated/schema"; -import { getOrInitResolverEntry } from "../mappingHelpers"; import { createEventID, initializeEventEntity, ZERO_ADDRESS } from "../utils"; export function handleRoleAdminChanged(event: RoleAdminChanged): void { const eventId = createEventID("RoleAdminChanged", event); const ev = new RoleAdminChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.previousAdminRole, event.params.newAdminRole]); ev.role = event.params.role; ev.previousAdminRole = event.params.previousAdminRole; @@ -29,7 +24,7 @@ export function handleRoleAdminChanged(event: RoleAdminChanged): void { export function handleRoleGranted(event: RoleGranted): void { const eventId = createEventID("RoleGranted", event); const ev = new RoleGrantedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.account, event.params.sender]); ev.role = event.params.role; ev.account = event.params.account; @@ -39,7 +34,7 @@ export function handleRoleGranted(event: RoleGranted): void { export function handleRoleRevoked(event: RoleRevoked): void { const eventId = createEventID("RoleRevoked", event); const ev = new RoleRevokedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.account, event.params.sender]); ev.role = event.params.role; ev.account = event.params.account; @@ -47,14 +42,34 @@ export function handleRoleRevoked(event: RoleRevoked): void { ev.save(); } +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 isListed = target.notEqual(ZERO_ADDRESS); + + // we only update this if the target is not equal to the zero address + if (isListed) { + resolverEntry.isToken = Token.load(target.toHex()) != null; + } + resolverEntry.updatedAtBlockNumber = block.number; + resolverEntry.updatedAtTimestamp = block.timestamp; + resolverEntry.isListed = isListed; + + resolverEntry.save(); + + return resolverEntry as ResolverEntry; +} + 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 - ); + const resolverEntry = getOrInitResolverEntry(event.params.name.toHex(), event.params.target, event.block); // upon initial setting, we will know if this address belongs to a token contract if (resolverEntry.isToken) { @@ -80,11 +95,7 @@ export function handleSet(event: Set): void { resolverEntry.save(); } -function _createSetEvent( - event: ethereum.Event, - target: Bytes, - name: Bytes -): void { +function _createSetEvent(event: ethereum.Event, target: Bytes, name: Bytes): void { const eventId = createEventID("Set", event); const ev = new SetEvent(eventId); initializeEventEntity(ev, event, [target]); diff --git a/packages/subgraph/src/mappings/superToken.ts b/packages/subgraph/src/mappings/superToken.ts index a6e10d496d..18be4b2050 100644 --- a/packages/subgraph/src/mappings/superToken.ts +++ b/packages/subgraph/src/mappings/superToken.ts @@ -12,6 +12,7 @@ import { import { AgreementLiquidatedByEvent, AgreementLiquidatedV2Event, + ApprovalEvent, BurnedEvent, MintedEvent, SentEvent, @@ -452,6 +453,17 @@ function _createTransferEventEntity(event: Transfer): void { } export function handleApproval(event: Approval): void { + const eventId = createEventID("Approval", event); + const ev = new ApprovalEvent(eventId); + initializeEventEntity(ev, event, [event.address, event.params.owner, event.params.spender]); + ev.owner = event.params.owner.toHex(); + ev.to = event.params.spender.toHex(); + ev.tokenId = BIG_INT_ZERO; + ev.amount = event.params.value; + ev.isNFTApproval = false; + + ev.save(); + // The entity named `FlowOperators` which currently holds all the user access and approval settings will be renamed to `AccessSettings`. const flowOperator = getOrInitFlowOperator( event.block, diff --git a/packages/subgraph/src/mappings/superTokenFactory.ts b/packages/subgraph/src/mappings/superTokenFactory.ts index d8d3bdf701..e65b939daa 100644 --- a/packages/subgraph/src/mappings/superTokenFactory.ts +++ b/packages/subgraph/src/mappings/superTokenFactory.ts @@ -59,7 +59,7 @@ export function handleSuperTokenLogicCreated( } const eventId = createEventID("SuperTokenLogicCreated", event); const ev = new SuperTokenLogicCreatedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.params.tokenLogic]); ev.tokenLogic = event.params.tokenLogic; ev.save(); diff --git a/packages/subgraph/src/mappings/superfluidGovernance.ts b/packages/subgraph/src/mappings/superfluidGovernance.ts index bbee404b60..a8432dc228 100644 --- a/packages/subgraph/src/mappings/superfluidGovernance.ts +++ b/packages/subgraph/src/mappings/superfluidGovernance.ts @@ -21,7 +21,7 @@ import { getOrInitTokenGovernanceConfig } from "../mappingHelpers"; export function handleConfigChanged(event: ConfigChanged): void { const eventId = createEventID("ConfigChanged", event); const ev = new ConfigChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.host, event.params.superToken]) ev.governanceAddress = event.address; ev.host = event.params.host; @@ -35,7 +35,7 @@ export function handleConfigChanged(event: ConfigChanged): void { export function handleRewardAddressChanged(event: RewardAddressChanged): void { const eventId = createEventID("RewardAddressChanged", event); const ev = new RewardAddressChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.host, event.params.superToken, event.params.rewardAddress]); ev.governanceAddress = event.address; ev.host = event.params.host; @@ -66,7 +66,7 @@ export function handleCFAv1LiquidationPeriodChanged( ): void { const eventId = createEventID("CFAv1LiquidationPeriodChanged", event); const ev = new CFAv1LiquidationPeriodChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.host, event.params.superToken]); ev.governanceAddress = event.address; ev.host = event.params.host; @@ -91,7 +91,7 @@ export function handlePPPConfigurationChanged( ): void { const eventId = createEventID("PPPConfigurationChanged", event); const ev = new PPPConfigurationChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.host, event.params.superToken]); ev.governanceAddress = event.address; ev.host = event.params.host; @@ -118,7 +118,12 @@ export function handleTrustedForwarderChanged( ): void { const eventId = createEventID("TrustedForwarderChanged", event); const ev = new TrustedForwarderChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [ + event.address, + event.params.host, + event.params.superToken, + event.params.forwarder, + ]); ev.governanceAddress = event.address; ev.host = event.params.host; @@ -134,7 +139,7 @@ export function handleSuperTokenMinimumDepositChanged( ): void { const eventId = createEventID("SuperTokenMinimumDepositChanged", event); const ev = new SuperTokenMinimumDepositChangedEvent(eventId); - initializeEventEntity(ev, event, []); + initializeEventEntity(ev, event, [event.address, event.params.host, event.params.superToken]); ev.governanceAddress = event.address; ev.host = event.params.host; diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index b018d2377a..850818c242 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -8,10 +8,10 @@ import { _createAccountTokenSnapshotLogEntity, _createTokenStatisticLogEntity, getOrInitPool, - getOrInitPoolMember, + getOrInitOrUpdatePoolMember, + settlePDPoolMemberMU, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, - updatePoolMemberTotalAmountUntilUpdatedAtFields, updatePoolTotalAmountFlowedAndDistributed, updateTokenStatsStreamedUntilUpdatedAt, } from "../mappingHelpers"; @@ -24,14 +24,17 @@ export function handleDistributionClaimed(event: DistributionClaimed): void { // Update Pool let pool = getOrInitPool(event, event.address.toHex()); - pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); - pool.save(); + let poolMember = getOrInitOrUpdatePoolMember(event, event.address, event.params.member); + poolMember.totalAmountClaimed = event.params.totalClaimed; + // settle pool and pool member + pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); + // Update PoolMember - let poolMember = getOrInitPoolMember(event, event.address, event.params.member); poolMember.totalAmountClaimed = event.params.totalClaimed; - - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); + + pool.save(); poolMember.save(); // Update Token Statistics @@ -48,38 +51,42 @@ export function handleDistributionClaimed(event: DistributionClaimed): void { } export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { - // - PoolMember - // - units - let poolMember = getOrInitPoolMember(event, event.address, event.params.member); - const hasMembershipWithUnits = membershipWithUnitsExists(poolMember.id); - let pool = getOrInitPool(event, event.address.toHex()); + let poolMember = getOrInitOrUpdatePoolMember(event, event.address, event.params.member); const previousUnits = poolMember.units; const unitsDelta = event.params.newUnits.minus(previousUnits); + const newTotalUnits = pool.totalUnits.plus(unitsDelta); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); + const existingPoolFlowRate = pool.perUnitFlowRate.times(pool.totalUnits); + let newPerUnitFlowRate: BigInt; + let remainderRate: BigInt; - poolMember.units = event.params.newUnits; - - const eventName = "MemberUnitsUpdated"; - updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block); - _createTokenStatisticLogEntity(event, event.params.token, eventName); + if (!newTotalUnits.equals(BIG_INT_ZERO)) { + newPerUnitFlowRate = existingPoolFlowRate.div(newTotalUnits); + remainderRate = existingPoolFlowRate.minus(newPerUnitFlowRate.times(newTotalUnits)); + } else { + remainderRate = existingPoolFlowRate; + newPerUnitFlowRate = BIG_INT_ZERO; + } + pool.perUnitFlowRate = newPerUnitFlowRate; + pool.totalUnits = newTotalUnits; - updateATSStreamedAndBalanceUntilUpdatedAt(event.params.member, event.params.token, event.block, null); - _createAccountTokenSnapshotLogEntity(event, event.params.member, event.params.token, eventName); + poolMember.syncedPerUnitFlowRate = poolMember.syncedPerUnitFlowRate.plus(remainderRate); + poolMember.units = event.params.newUnits; if (poolMember.isConnected) { pool.totalConnectedUnits = pool.totalConnectedUnits.plus(unitsDelta); } else { pool.totalDisconnectedUnits = pool.totalDisconnectedUnits.plus(unitsDelta); } - pool.totalUnits = pool.totalUnits.plus(unitsDelta); // 0 units to > 0 units - if (previousUnits.equals(BIG_INT_ZERO) && event.params.newUnits.gt(BIG_INT_ZERO)) { + const didPoolMemberBecomeActive = previousUnits.equals(BIG_INT_ZERO) && event.params.newUnits.gt(BIG_INT_ZERO) + if (didPoolMemberBecomeActive) { pool.totalMembers = pool.totalMembers + 1; // if the member is connected with units now, we add one to connected if (poolMember.isConnected) { @@ -92,7 +99,7 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { updateAggregateDistributionAgreementData( event.params.member, event.params.token, - hasMembershipWithUnits, + true, // has units poolMember.isConnected, true, // only place we increment subWithUnits false, // not deleting @@ -102,8 +109,10 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { false // isIDA ); } + // > 0 units to 0 units - if (previousUnits.gt(BIG_INT_ZERO) && poolMember.units.equals(BIG_INT_ZERO)) { + const didPoolMemberBecomeInactive = previousUnits.gt(BIG_INT_ZERO) && poolMember.units.equals(BIG_INT_ZERO) + if (didPoolMemberBecomeInactive) { pool.totalMembers = pool.totalMembers - 1; // if the member is connected with no units now, we subtract one from connected if (poolMember.isConnected) { @@ -116,7 +125,7 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { updateAggregateDistributionAgreementData( event.params.member, event.params.token, - hasMembershipWithUnits, + false, // has units poolMember.isConnected, false, // don't increment memberWithUnits false, // not disconnecting membership @@ -132,6 +141,14 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { // Create Event Entity _createMemberUnitsUpdatedEntity(event, poolMember.id, pool.totalUnits); + + // Other entity updates + const eventName = "MemberUnitsUpdated"; + updateTokenStatsStreamedUntilUpdatedAt(event.params.token, event.block); + _createTokenStatisticLogEntity(event, event.params.token, eventName); + + updateATSStreamedAndBalanceUntilUpdatedAt(event.params.member, event.params.token, event.block, null); + _createAccountTokenSnapshotLogEntity(event, event.params.member, event.params.token, eventName); } function _createDistributionClaimedEntity(event: DistributionClaimed, poolMemberId: string): DistributionClaimedEvent { diff --git a/packages/subgraph/src/utils.ts b/packages/subgraph/src/utils.ts index a182123f3c..f7b84b85e7 100644 --- a/packages/subgraph/src/utils.ts +++ b/packages/subgraph/src/utils.ts @@ -8,12 +8,14 @@ import { log, Value, } from "@graphprotocol/graph-ts"; -import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; import { IndexSubscription, - Token, PoolMember, + Token, } from "../generated/schema"; +import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; +import { Resolver } from "../generated/templates/SuperToken/Resolver"; +import { getIsLocalIntegrationTesting } from "./addresses"; /************************************************************************** * Constants @@ -108,8 +110,7 @@ export function initializeEventEntity( *************************************************************************/ export function handleTokenRPCCalls( - token: Token, - resolverAddress: Address + token: Token ): Token { // we must handle the case when the native token hasn't been initialized // there is no name/symbol, but this may occur later @@ -119,6 +120,20 @@ export function handleTokenRPCCalls( return token; } +export function getIsTokenListed( + token: Token, + resolverAddress: Address +): boolean { + const resolverContract = Resolver.bind(resolverAddress); + const isLocalIntegrationTesting = getIsLocalIntegrationTesting(); + const version = isLocalIntegrationTesting ? "test" : "v1"; + const result = resolverContract.try_get( + "supertokens." + version + "." + token.symbol + ); + const superTokenAddress = result.reverted ? ZERO_ADDRESS : result.value; + return token.id == superTokenAddress.toHex(); +} + export function getTokenInfoAndReturn(token: Token): Token { const tokenAddress = Address.fromString(token.id); const tokenContract = SuperToken.bind(tokenAddress); @@ -387,3 +402,12 @@ export function createLogID( event.logIndex.toString() ); } + +export function divideOrZero( + numerator: BigInt, + denominator: BigInt +): BigInt { + return denominator.equals(BIG_INT_ZERO) + ? BIG_INT_ZERO + : numerator.div(denominator); +} \ No newline at end of file diff --git a/packages/subgraph/tests/bugs/2024-02-29-aleph-total-supply.test.ts b/packages/subgraph/tests/bugs/2024-02-29-aleph-total-supply.test.ts index bfb7412813..2862a3c879 100644 --- a/packages/subgraph/tests/bugs/2024-02-29-aleph-total-supply.test.ts +++ b/packages/subgraph/tests/bugs/2024-02-29-aleph-total-supply.test.ts @@ -3,7 +3,7 @@ import { assert, describe, test } from "matchstick-as"; import { handleMinted } from "../../src/mappings/superToken"; import { handleCustomSuperTokenCreated } from "../../src/mappings/superTokenFactory"; import { ZERO_ADDRESS } from "../../src/utils"; -import { bob, alice } from "../constants"; +import { bob, alice, resolverAddress } from "../constants"; import { stringToBytes } from "../converters"; import { mockedGetHost, @@ -12,6 +12,7 @@ import { mockedTokenSymbol, mockedTokenDecimals, mockedTokenTotalSupply, + mockedResolverGet, } from "../mockedFunctions"; import { createMintedEvent } from "../superToken/superToken.helper"; import { createCustomSuperTokenCreatedEvent } from "../superTokenFactory/superTokenFactory.helper"; @@ -30,6 +31,7 @@ describe("ALEPH Total Supply Bug", () => { mockedTokenName(superToken, "tokenName"); mockedTokenSymbol(superToken, "tokenSymbol"); mockedTokenDecimals(superToken, 18); + mockedResolverGet(resolverAddress, "supertokens.v1.tokenSymbol", ZERO_ADDRESS.toHexString()); // unused mocked function call after change in this commit (removing total supply RPC call in getOrInitSuperToken) mockedTokenTotalSupply(superToken, totalSupply); diff --git a/packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts b/packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts new file mode 100644 index 0000000000..f4053da76a --- /dev/null +++ b/packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts @@ -0,0 +1,89 @@ +import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; +import { createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; +import { Address, BigInt } from "@graphprotocol/graph-ts"; +import { FAKE_INITIAL_BALANCE, alice, bob, charlie } from "../constants"; +import { BIG_INT_ZERO, getPoolMemberID } from "../../src/utils"; +import { assert, describe, test } from "matchstick-as"; +import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions"; + +describe("PoolMember not updating when units changed", () => { + test("emit MemberUnitsUpdated event", () => { + const superTokenAddress = alice; + + const poolAddress = Address.fromString(bob); + const poolMemberAccountAddress = Address.fromString(charlie); + const poolMemberId = getPoolMemberID(poolAddress, poolMemberAccountAddress); + + mockedGetAppManifest(poolMemberAccountAddress.toHexString(), false, false, BIG_INT_ZERO); + + // Initialize pool member for the first time + const firstEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + poolMemberAccountAddress.toHexString(), + BigInt.fromI32(0), // old units + BigInt.fromI32(0) // new units + ); + firstEvent.address = poolAddress; + + mockedRealtimeBalanceOf( + superTokenAddress, + poolMemberAccountAddress.toHexString(), + firstEvent.block.timestamp, + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO + ); + + handleMemberUnitsUpdated(firstEvent); + // --- + + + const newUnits = BigInt.fromI32(100); + const blockNumber = BigInt.fromI32(200) + const timestamp = BigInt.fromI32(300) + + const secondEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + poolMemberAccountAddress.toHexString(), + BigInt.fromI32(0), // old units + newUnits + ); + secondEvent.block.timestamp = timestamp + secondEvent.address = poolAddress; + secondEvent.block.number = blockNumber; + + mockedRealtimeBalanceOf( + superTokenAddress, + poolMemberAccountAddress.toHexString(), + secondEvent.block.timestamp, + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO + ); + + // Act + handleMemberUnitsUpdated(secondEvent); + + // Assert + assert.fieldEquals( + "PoolMember", + poolMemberId, + "units", + newUnits.toString() + ); + + assert.fieldEquals( + "PoolMember", + poolMemberId, + "updatedAtTimestamp", + timestamp.toString() + ); + + assert.fieldEquals( + "PoolMember", + poolMemberId, + "updatedAtBlockNumber", + blockNumber.toString() + ); + }); +}); diff --git a/packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts b/packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts new file mode 100644 index 0000000000..fdd04955af --- /dev/null +++ b/packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts @@ -0,0 +1,481 @@ +import { assert, beforeEach, clearStore, describe, test } from "matchstick-as"; +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { alice as alice_, bob as bob_, delta, echo, maticXAddress, superfluidPool } from "../constants"; +import { getPoolMemberID } from "../../src/utils"; +import { handleFlowDistributionUpdated, handleInstantDistributionUpdated, handlePoolCreated } from "../../src/mappings/gdav1"; +import { createFlowDistributionUpdatedEvent, createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent, createPoolAndReturnPoolCreatedEvent } from "../gdav1/gdav1.helper"; +import { mockedAppManifestAndRealtimeBalanceOf } from "../mockedFunctions"; +import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; +import { Pool } from "../../generated/schema"; + +describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", () => { + beforeEach(() => { + clearStore(); + }); + + /** + * Problem description + 1. Create pool + 2. Add member A and update A units to 100 + 3. Distribute 1000 tokens + 4. Add member B and update B units to 100 + 5. Distribute 1000 tokens + + Expected result: + member A 1500 tokens + member B 500 tokens + */ + test("create elaborate scenario with 2 instant distributions and 2 pool members", () => { + const superTokenAddress = maticXAddress; + const poolAdminAndDistributorAddress = Address.fromString(delta); + const poolAddress = Address.fromString(superfluidPool); + + // # Arrange State 1 + // ## Arrange Pool + const poolCreatedEvent = createPoolAndReturnPoolCreatedEvent(poolAdminAndDistributorAddress.toHexString(), superTokenAddress, poolAddress.toHexString()); + + // ## Arrange PoolMember 1 + const aliceAddress = Address.fromString(alice_); + const aliceId = getPoolMemberID(poolAddress, aliceAddress); + const aliceCreatedEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(0), // old units + BigInt.fromI32(100) // new units + ); + aliceCreatedEvent.address = poolAddress; + aliceCreatedEvent.block.timestamp = poolCreatedEvent.block.timestamp; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), aliceCreatedEvent.block.timestamp); + handleMemberUnitsUpdated(aliceCreatedEvent); + + // # First distribution + const firstDistributionEvent = createInstantDistributionUpdatedEvent( + superTokenAddress, + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(1000), // requested amount + BigInt.fromI32(1000), // actual amount + Bytes.fromHexString("0x") + ); + firstDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; + firstDistributionEvent.address = poolAddress; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), firstDistributionEvent.block.timestamp); + handleInstantDistributionUpdated(firstDistributionEvent); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "1000" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "100" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalMembers", + "1" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "0" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" + ); + // --- + + // # Arrange State 2 + // ## Arrange PoolMember 2 (new member) + const bobAddress = Address.fromString(bob_); + const bobId = getPoolMemberID(poolAddress, bobAddress); + let createBobEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(0), // old units + BigInt.fromI32(100) // new units + ); + createBobEvent.address = poolAddress; + createBobEvent.block.timestamp = BigInt.fromI32(2); + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), createBobEvent.block.timestamp); + handleMemberUnitsUpdated(createBobEvent); + + // # Second distribution + const secondDistributionEvent = createInstantDistributionUpdatedEvent( + superTokenAddress, + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(1000), // requested amount + BigInt.fromI32(1000), // actual amount + Bytes.fromHexString("0x") + ); + secondDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; + secondDistributionEvent.address = poolAddress; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), secondDistributionEvent.block.timestamp); + handleInstantDistributionUpdated(secondDistributionEvent); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "2000" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "200" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "0" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); + // --- + + // Arrange State 3 + // # Update PoolMember 2's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateBobEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateBobEvent.address = poolAddress; + updateBobEvent.block.timestamp = BigInt.fromI32(3); + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), updateBobEvent.block.timestamp); + handleMemberUnitsUpdated(createBobEvent); + + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "500" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); + + // # Update PoolMember 1's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateAliceEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateAliceEvent.address = poolAddress; + updateAliceEvent.block.timestamp = BigInt.fromI32(3); + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), updateAliceEvent.block.timestamp); + handleMemberUnitsUpdated(updateAliceEvent); + + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "1500" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" + ); + }); + /** + * Problem description + 1. Create pool + 2. Add member A and update A units to 100 + 3. Flow 1000 tokens (elapse 1 second) + 4. Add member B and update B units to 100 + 5. Flow 1000 tokens (elapse 1 second) + 6. Update flow rate to 2000 tokens + 7. Flow 2000 tokens (elapse 1 second) + + Expected result: + member A 2500 tokens + member B 1500 tokens + + Actual result: + member A 100 tokens + member B 50 tokens + */ + test("create elaborate scenario with 2 flowing distributions and 2 pool members", () => { + const superTokenAddress = maticXAddress; + const poolAdminAndDistributorAddress = Address.fromString(delta); + const poolAddress = Address.fromString(superfluidPool); + + // # Arrange State 1 + // ## Arrange Pool + const poolCreatedEvent = createPoolAndReturnPoolCreatedEvent(poolAdminAndDistributorAddress.toHexString(), superTokenAddress, poolAddress.toHexString()); + assert.stringEquals(poolCreatedEvent.block.timestamp.toString(), BigInt.fromI32(1).toString()); + + handlePoolCreated(poolCreatedEvent); + + const pool = Pool.load(poolAddress.toHexString()); + + if (pool) { + pool.updatedAtTimestamp = poolCreatedEvent.block.timestamp; + } + + // ## Arrange PoolMember 1 + const aliceAddress = Address.fromString(alice_); + const aliceId = getPoolMemberID(poolAddress, aliceAddress); + const aliceCreatedEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(0), // old units + BigInt.fromI32(100) // new units + ); + aliceCreatedEvent.address = poolAddress; + aliceCreatedEvent.block.timestamp = poolCreatedEvent.block.timestamp; // 1 + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), aliceCreatedEvent.block.timestamp); + handleMemberUnitsUpdated(aliceCreatedEvent); + + // # First flow rate + if (pool) { + pool.updatedAtTimestamp = aliceCreatedEvent.block.timestamp; + } + + const firstFlowRateEvent = createFlowDistributionUpdatedEvent( + superTokenAddress, + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(0), // oldFlowRate + BigInt.fromI32(1000), // newDistributorToPoolFlowRate + BigInt.fromI32(1000), // newTotalDistributionFlowRate + poolAdminAndDistributorAddress.toHexString(), // adjustmentFlowRecipient + BigInt.fromI32(0), + Bytes.fromHexString("0x") + ); + firstFlowRateEvent.block.timestamp = poolCreatedEvent.block.timestamp; // 1 + firstFlowRateEvent.address = poolAddress; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), firstFlowRateEvent.block.timestamp); + handleFlowDistributionUpdated(firstFlowRateEvent); + + // # First flow rate + if (pool) { + pool.updatedAtTimestamp = firstFlowRateEvent.block.timestamp; + } + + // TODO: This fails, how has this already flown??? + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "0" // nothing is flowed yet + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "100" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalMembers", + "1" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "0" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" + ); + // --- + + // # Arrange State 2 + // ## Arrange PoolMember 2 (new member) + const bobAddress = Address.fromString(bob_); + const bobId = getPoolMemberID(poolAddress, bobAddress); + let createBobEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(0), // old units + BigInt.fromI32(100) // new units + ); + createBobEvent.address = poolAddress; + createBobEvent.block.timestamp = BigInt.fromI32(2); // Skip 1 second to let it flow to Alice + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), createBobEvent.block.timestamp); + handleMemberUnitsUpdated(createBobEvent); + + if (pool) { + pool.updatedAtTimestamp = createBobEvent.block.timestamp; + } + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "1000" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "0" // Bob just joined, shouldn't have any received + ); + + // # Second flow rate + const secondFlowRateEvent = createFlowDistributionUpdatedEvent( + superTokenAddress, + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(1000), // oldFlowRate + BigInt.fromI32(2000), // newDistributorToPoolFlowRate + BigInt.fromI32(2000), // newTotalDistributionFlowRate + poolAdminAndDistributorAddress.toHexString(), // adjustmentFlowRecipient + BigInt.fromI32(0), + Bytes.fromHexString("0x") + ); + secondFlowRateEvent.block.timestamp = BigInt.fromI32(3); // One second skipped, 2 seconds flown to Alice, 1 second to Bob + secondFlowRateEvent.address = poolAddress; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), secondFlowRateEvent.block.timestamp); + handleFlowDistributionUpdated(secondFlowRateEvent); + + if (pool) { + pool.updatedAtTimestamp = secondFlowRateEvent.block.timestamp; + } + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "2000" // Only for first flow rate + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "200" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "0" // it's 500 if we query on-chain, but 0 here because update member units hasn't been called again since + ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "0" // it's 1500 if we query on-chain, but 0 here because update member units hasn't been called again since + ); + // --- + + // Arrange State 3 + // # Update PoolMember 2's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateBobEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateBobEvent.address = poolAddress; + updateBobEvent.block.timestamp = BigInt.fromI32(4); // 4 - 1 = 3 seconds of flow + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), updateBobEvent.block.timestamp); + handleMemberUnitsUpdated(updateBobEvent); + + if (pool) { + pool.updatedAtTimestamp = updateBobEvent.block.timestamp; + } + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "4000" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "1500" // 50% of 2000 + ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); + + // # Update PoolMember 1's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateAliceEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateAliceEvent.address = poolAddress; + updateAliceEvent.block.timestamp = updateBobEvent.block.timestamp; // 4 + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), updateAliceEvent.block.timestamp); + handleMemberUnitsUpdated(updateAliceEvent); + + if (pool) { + pool.updatedAtTimestamp = updateAliceEvent.block.timestamp; + } + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "2500" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" + ); + }) +}); + \ No newline at end of file diff --git a/packages/subgraph/tests/constants.ts b/packages/subgraph/tests/constants.ts index 1c15feacfc..11358dc9e9 100644 --- a/packages/subgraph/tests/constants.ts +++ b/packages/subgraph/tests/constants.ts @@ -26,7 +26,7 @@ export const cfaV1Address = "0x6eee6060f715257b970700bc2656de21dedf074c"; export const idaV1Address = "0xb0aabba4b2783a72c52956cdef62d438eca2d7a1"; export const superTokenFactoryAddress = "0x2c90719f25b10fc5646c82da3240c76fa5bccf34"; export const superTokenLogicAddress = "0x1349b5f1006ef0366a7b6ae41fa9155c6cd91e4b"; -export const resolverAddress = "0xe0cc76334405ee8b39213e620587d815967af39c"; +export const resolverAddress = "0x8bDCb5613153f41B2856F71Bd7A7e0432F6dbe58"; // this is not the actual TOGA export const togaAddress = "0x6aeaee5fd4d05a741723d752d30ee4d72690a8f7"; export const maticXAddress = "0x3ad736904e9e65189c3000c7dd2c8ac8bb7cd4e3"; diff --git a/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts b/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts index c0e2cfc3f7..9873aef7f1 100644 --- a/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts +++ b/packages/subgraph/tests/gdav1/hol/gdav1.hol.test.ts @@ -19,7 +19,7 @@ import { } from "../../../src/mappings/gdav1"; import { updateMemberUnitsAndReturnMemberUnitsUpdatedEvent } from "../gdav1.helper"; import { handleDistributionClaimed, handleMemberUnitsUpdated } from "../../../src/mappings/superfluidPool"; -import { getOrInitPoolMember } from "../../../src/mappingHelpers"; +import { getOrInitOrUpdatePoolMember } from "../../../src/mappingHelpers"; import { stringToBytes } from "../../converters"; const initialFlowRate = BigInt.fromI32(100); @@ -285,7 +285,7 @@ describe("GeneralDistributionAgreementV1 Higher Order Level Entity Unit Tests", const oldUnits = BigInt.fromI32(0); const newUnits = BigInt.fromI32(100000000); const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits); - const poolMemberEntity = getOrInitPoolMember( + const poolMemberEntity = getOrInitOrUpdatePoolMember( memberUnitsUpdatedEvent, memberUnitsUpdatedEvent.address, Address.fromString(poolMember) @@ -319,7 +319,7 @@ describe("GeneralDistributionAgreementV1 Higher Order Level Entity Unit Tests", const oldUnits = BigInt.fromI32(0); const newUnits = BigInt.fromI32(100000000); const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits); - const poolMemberEntity = getOrInitPoolMember( + const poolMemberEntity = getOrInitOrUpdatePoolMember( memberUnitsUpdatedEvent, memberUnitsUpdatedEvent.address, Address.fromString(poolMember) @@ -362,7 +362,7 @@ describe("GeneralDistributionAgreementV1 Higher Order Level Entity Unit Tests", const oldUnits = BigInt.fromI32(0); const newUnits = BigInt.fromI32(100000000); const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits); - const poolMemberEntity = getOrInitPoolMember( + const poolMemberEntity = getOrInitOrUpdatePoolMember( memberUnitsUpdatedEvent, memberUnitsUpdatedEvent.address, Address.fromString(poolMember) @@ -395,7 +395,7 @@ describe("GeneralDistributionAgreementV1 Higher Order Level Entity Unit Tests", const oldUnits = BigInt.fromI32(0); const newUnits = BigInt.fromI32(100000000); const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent(superToken, poolMember, oldUnits, newUnits); - const poolMemberEntity = getOrInitPoolMember( + const poolMemberEntity = getOrInitOrUpdatePoolMember( memberUnitsUpdatedEvent, memberUnitsUpdatedEvent.address, Address.fromString(poolMember) diff --git a/packages/subgraph/tests/idav1/event/idav1.event.test.ts b/packages/subgraph/tests/idav1/event/idav1.event.test.ts index 7bc6e08288..c343d17049 100644 --- a/packages/subgraph/tests/idav1/event/idav1.event.test.ts +++ b/packages/subgraph/tests/idav1/event/idav1.event.test.ts @@ -14,9 +14,9 @@ import { } from "../../../src/mappings/idav1"; import { BIG_INT_ZERO, getIndexID, ZERO_ADDRESS } from "../../../src/utils"; import { assertIDAEventBaseProperties, assertIDAIndexEventBaseProperties } from "../../assertionHelpers"; -import { alice, bob, DEFAULT_DECIMALS, FAKE_INITIAL_BALANCE, maticXAddress, maticXName, maticXSymbol } from "../../constants"; +import { alice, bob, DEFAULT_DECIMALS, FAKE_INITIAL_BALANCE, maticXAddress, maticXName, maticXSymbol, resolverAddress } from "../../constants"; import { stringToBytes } from "../../converters"; -import { mockedGetAppManifest, mockedGetHost, mockedHandleSuperTokenInitRPCCalls, mockedRealtimeBalanceOf } from "../../mockedFunctions"; +import { mockedGetAppManifest, mockedGetHost, mockedHandleSuperTokenInitRPCCalls, mockedRealtimeBalanceOf, mockedResolverGet } from "../../mockedFunctions"; import { createIndexCreatedEvent, createIndexDistributionClaimedEvent, @@ -267,6 +267,8 @@ describe("InstantDistributionV1 Event Entity Unit Tests", () => { userData ); + mockedResolverGet(resolverAddress, "supertokens.v1.MATICx", ZERO_ADDRESS.toHexString()); + mockedRealtimeBalanceOf( superToken, subscriber, diff --git a/packages/subgraph/tests/mockedFunctions.ts b/packages/subgraph/tests/mockedFunctions.ts index 88629bdc57..ba9aa3e4b6 100644 --- a/packages/subgraph/tests/mockedFunctions.ts +++ b/packages/subgraph/tests/mockedFunctions.ts @@ -1,11 +1,12 @@ import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; import { createMockedFunction } from "matchstick-as/assembly/index"; import { FlowUpdated } from "../generated/ConstantFlowAgreementV1/IConstantFlowAgreementV1"; -import { BIG_INT_ZERO } from "../src/utils"; +import { BIG_INT_ZERO, ZERO_ADDRESS } from "../src/utils"; import { FAKE_INITIAL_BALANCE, FAKE_SUPER_TOKEN_TOTAL_SUPPLY, hostAddress, + resolverAddress, } from "./constants"; import { getETHAddress, @@ -46,6 +47,8 @@ export function mockedHandleSuperTokenInitRPCCalls( mockedTokenDecimals(superToken, decimals); // [END] getTokenInfoAndReturn + mockedResolverGet(resolverAddress, "supertokens.v1." + tokenSymbol, ZERO_ADDRESS.toHexString()); + // updateTotalSupplyForNativeSuperToken(token, tokenStatistic, tokenAddress) mockedTokenTotalSupply(superToken, FAKE_SUPER_TOKEN_TOTAL_SUPPLY); } @@ -95,7 +98,7 @@ export function mockedHandleFlowUpdatedRPCCalls( // getOrInitStream(event) => getOrInitAccount(receiver) => host.try_getAppManifest(receiver) mockedGetAppManifest(receiver, false, false, BIG_INT_ZERO); - // [START] getOrInitStream(event) => getOrInitSuperToken(token, block) => handleTokenRPCCalls(token, resolverAddress) + // [START] getOrInitStream(event) => getOrInitSuperToken(token, block) => handleTokenRPCCalls(token) mockedHandleSuperTokenInitRPCCalls( superToken, decimals, @@ -103,7 +106,9 @@ export function mockedHandleFlowUpdatedRPCCalls( tokenName, tokenSymbol ); - // [END] getOrInitStream(event) => getOrInitSuperToken(token, block) => handleTokenRPCCalls(token, resolverAddress) + // [END] getOrInitStream(event) => getOrInitSuperToken(token, block) => handleTokenRPCCalls(token) + + mockedResolverGet(resolverAddress, "supertokens.v1." + tokenSymbol, ZERO_ADDRESS.toHexString()); // updateATSStreamedAndBalanceUntilUpdatedAt => updateATSBalanceAndUpdatedAt => try_realtimeBalanceOf(sender) mockedRealtimeBalanceOf( @@ -379,3 +384,19 @@ export function mockedApprove( ]) .returns([getETHUnsignedBigInt(expectedValue)]); } + +export function mockedAppManifestAndRealtimeBalanceOf( + tokenAddress: string, + accountAddress: string, + timestamp: BigInt +): void { + mockedGetAppManifest(accountAddress, false, false, BIG_INT_ZERO); + mockedRealtimeBalanceOf( + tokenAddress, + accountAddress, + timestamp, + FAKE_INITIAL_BALANCE, + BIG_INT_ZERO, + BIG_INT_ZERO + ); +} diff --git a/packages/subgraph/tests/superToken/event/superToken.event.test.ts b/packages/subgraph/tests/superToken/event/superToken.event.test.ts index f515b4165a..b04f280ba2 100644 --- a/packages/subgraph/tests/superToken/event/superToken.event.test.ts +++ b/packages/subgraph/tests/superToken/event/superToken.event.test.ts @@ -9,6 +9,7 @@ import { import { handleAgreementLiquidatedBy, handleAgreementLiquidatedV2, + handleApproval, handleBurned, handleMinted, handleSent, @@ -18,7 +19,7 @@ import { } from "../../../src/mappings/superToken"; import { BIG_INT_ONE, BIG_INT_ZERO, encode, ZERO_ADDRESS } from "../../../src/utils"; import { assertEmptyTokenStatisticProperties, assertEventBaseProperties, assertTokenStatisticProperties } from "../../assertionHelpers"; -import { alice, bob, cfaV1Address, charlie, DEFAULT_DECIMALS, delta, FAKE_INITIAL_BALANCE, maticXName, maticXSymbol } from "../../constants"; +import { alice, bob, cfaV1Address, charlie, DEFAULT_DECIMALS, delta, FAKE_INITIAL_BALANCE, FALSE, maticXName, maticXSymbol, TRUE } from "../../constants"; import { getETHAddress, getETHUnsignedBigInt, stringToBytes } from "../../converters"; import { createStream, createStreamRevision } from "../../mockedEntities"; import { mockedGetAppManifest, mockedGetHost, mockedHandleSuperTokenInitRPCCalls, mockedRealtimeBalanceOf } from "../../mockedFunctions"; @@ -28,6 +29,7 @@ import { createBurnedEvent, createMintedEvent, createSentEvent, + createSuperTokenApprovalEvent, createTokenDowngradedEvent, createTokenUpgradedEvent, createTransferEvent, @@ -337,6 +339,30 @@ describe("SuperToken Mapper Unit Tests", () => { assert.fieldEquals("TokenDowngradedEvent", id, "amount", amount.toString()); }); + test("handleApproval() - Should create a new ApprovalEvent entity", () => { + const owner = alice; + const spender = bob; + const value = BigInt.fromI32(100); + + const superTokenApprovalEvent = createSuperTokenApprovalEvent( + owner, + spender, + value + ); + + handleApproval(superTokenApprovalEvent); + + const id = assertEventBaseProperties( + superTokenApprovalEvent, + "Approval" + ); + assert.fieldEquals("ApprovalEvent", id, "owner", owner); + assert.fieldEquals("ApprovalEvent", id, "to", spender); + assert.fieldEquals("ApprovalEvent", id, "amount", value.toString()); + assert.fieldEquals("ApprovalEvent", id, "isNFTApproval", FALSE); + assert.fieldEquals("ApprovalEvent", id, "tokenId", "0"); + }); + test("handleTransfer() - Should create a new TransferEvent entity", () => { const from = alice; const to = bob; @@ -480,7 +506,7 @@ describe("SuperToken Mapper Unit Tests", () => { "operatorData", operatorData.toHexString() ); - }); + }); test("TokenStatistic::totalNumberOfHolders should decrease its count when a user transfers tokens and the balance reaches 0.", () => { const from = alice; diff --git a/packages/subgraph/tests/superToken/superToken.helper.ts b/packages/subgraph/tests/superToken/superToken.helper.ts index a8f9d82ae3..4ac930cdf8 100644 --- a/packages/subgraph/tests/superToken/superToken.helper.ts +++ b/packages/subgraph/tests/superToken/superToken.helper.ts @@ -77,6 +77,20 @@ export function createTokenUpgradedEvent( return newTokenUpgradedEvent; } +export function createSuperTokenApprovalEvent( + owner: string, + spender: string, + value: BigInt +): Approval { + const newApprovalEvent = changetype(newMockEvent()); + newApprovalEvent.parameters = new Array(); + newApprovalEvent.parameters.push(getAddressEventParam("owner", owner)); + newApprovalEvent.parameters.push(getAddressEventParam("spender", spender)); + newApprovalEvent.parameters.push(getBigIntEventParam("value", value)); + + return newApprovalEvent; +} + export function createTokenDowngradedEvent( account: string, amount: BigInt