From 9dad90540c837a7eaa2ee4e9c5d43145983d7313 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 13:37:41 +0200 Subject: [PATCH 01/17] add a test starter --- .../subgraph/tests/bugs/2024-02-29-aleph-total-supply.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..5a85bab1e6 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 @@ -30,7 +30,7 @@ describe("ALEPH Total Supply Bug", () => { mockedTokenName(superToken, "tokenName"); mockedTokenSymbol(superToken, "tokenSymbol"); mockedTokenDecimals(superToken, 18); - + // unused mocked function call after change in this commit (removing total supply RPC call in getOrInitSuperToken) mockedTokenTotalSupply(superToken, totalSupply); @@ -45,6 +45,6 @@ describe("ALEPH Total Supply Bug", () => { handleCustomSuperTokenCreated(customSuperTokenCreatedEvent); handleMinted(mintedEvent); - assert.fieldEquals("TokenStatistic", superToken, "totalSupply", totalSupply.toString()); + assert.fieldEquals("TokenStatistic", superToken, "totalSupply", "453452345"); }); }); From 70a8e5da37800c407075c260e0dbd90236af944b Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 13:40:59 +0200 Subject: [PATCH 02/17] add the test --- .../2024-02-29-aleph-total-supply.test.ts | 2 +- ...24-03-06-pool-member-units-changed.test.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts 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 5a85bab1e6..45d7a15c97 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 @@ -45,6 +45,6 @@ describe("ALEPH Total Supply Bug", () => { handleCustomSuperTokenCreated(customSuperTokenCreatedEvent); handleMinted(mintedEvent); - assert.fieldEquals("TokenStatistic", superToken, "totalSupply", "453452345"); + assert.fieldEquals("TokenStatistic", superToken, "totalSupply", totalSupply.toString()); }); }); 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..b87f208139 --- /dev/null +++ b/packages/subgraph/tests/bugs/2024-03-06-pool-member-units-changed.test.ts @@ -0,0 +1,38 @@ +import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; +import { createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; +import { Address, BigInt } from "@graphprotocol/graph-ts"; +import { alice, bob, charlie } from "../constants"; +import { getPoolMemberID } from "../../src/utils"; +import { assert, describe, test } from "matchstick-as"; + +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); + + const oldUnits = BigInt.fromI32(100); + const newUnits = BigInt.fromI32(200); + + const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + poolMemberId, + oldUnits, + newUnits + ); + memberUnitsUpdatedEvent.block.number = BigInt.fromI32(300); + + // // Act + handleMemberUnitsUpdated(memberUnitsUpdatedEvent); + + // // Assert + assert.fieldEquals( + "PoolMember", + poolMemberId, + "updatedAtBlockNumber", + memberUnitsUpdatedEvent.block.number.toString() + ); + }); +}); From 35c080bcd6ea84533994c749d7e674a37bc672f3 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Wed, 6 Mar 2024 13:58:32 +0200 Subject: [PATCH 03/17] fixes --- ...24-03-06-pool-member-units-changed.test.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) 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 index b87f208139..f681250603 100644 --- 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 @@ -1,9 +1,10 @@ import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; import { createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; import { Address, BigInt } from "@graphprotocol/graph-ts"; -import { alice, bob, charlie } from "../constants"; -import { getPoolMemberID } from "../../src/utils"; +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", () => { @@ -13,18 +14,35 @@ describe("PoolMember not updating when units changed", () => { const poolMemberAccountAddress = Address.fromString(charlie); const poolMemberId = getPoolMemberID(poolAddress, poolMemberAccountAddress); + const flowRate = BigInt.fromI32(69); const oldUnits = BigInt.fromI32(100); const newUnits = BigInt.fromI32(200); const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent( superTokenAddress, - poolMemberId, + poolMemberAccountAddress.toHexString(), oldUnits, newUnits ); memberUnitsUpdatedEvent.block.number = BigInt.fromI32(300); + memberUnitsUpdatedEvent.address = poolAddress; + + mockedGetAppManifest(poolMemberAccountAddress.toHexString(), false, false, BIG_INT_ZERO); + + mockedRealtimeBalanceOf( + superTokenAddress, + poolMemberAccountAddress.toHexString(), + memberUnitsUpdatedEvent.block.timestamp, + FAKE_INITIAL_BALANCE.minus(flowRate), + flowRate, + BIG_INT_ZERO + ); + + // Act + handleMemberUnitsUpdated(memberUnitsUpdatedEvent); + + memberUnitsUpdatedEvent.block.number = BigInt.fromI32(420); - // // Act handleMemberUnitsUpdated(memberUnitsUpdatedEvent); // // Assert From de476cc57d4e235d22a5fef12af4f887641cd2d9 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 14:23:53 +0000 Subject: [PATCH 04/17] improve test --- ...24-03-06-pool-member-units-changed.test.ts | 69 ++++++++++++++----- 1 file changed, 51 insertions(+), 18 deletions(-) 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 index f681250603..f4053da76a 100644 --- 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 @@ -14,43 +14,76 @@ describe("PoolMember not updating when units changed", () => { const poolMemberAccountAddress = Address.fromString(charlie); const poolMemberId = getPoolMemberID(poolAddress, poolMemberAccountAddress); - const flowRate = BigInt.fromI32(69); - const oldUnits = BigInt.fromI32(100); - const newUnits = BigInt.fromI32(200); + 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; - const memberUnitsUpdatedEvent = createMemberUnitsUpdatedEvent( + mockedRealtimeBalanceOf( superTokenAddress, poolMemberAccountAddress.toHexString(), - oldUnits, - newUnits + firstEvent.block.timestamp, + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO ); - memberUnitsUpdatedEvent.block.number = BigInt.fromI32(300); - memberUnitsUpdatedEvent.address = poolAddress; - mockedGetAppManifest(poolMemberAccountAddress.toHexString(), false, false, 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(), - memberUnitsUpdatedEvent.block.timestamp, - FAKE_INITIAL_BALANCE.minus(flowRate), - flowRate, + secondEvent.block.timestamp, + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), BIG_INT_ZERO ); - + // Act - handleMemberUnitsUpdated(memberUnitsUpdatedEvent); + handleMemberUnitsUpdated(secondEvent); - memberUnitsUpdatedEvent.block.number = BigInt.fromI32(420); + // Assert + assert.fieldEquals( + "PoolMember", + poolMemberId, + "units", + newUnits.toString() + ); - handleMemberUnitsUpdated(memberUnitsUpdatedEvent); + assert.fieldEquals( + "PoolMember", + poolMemberId, + "updatedAtTimestamp", + timestamp.toString() + ); - // // Assert assert.fieldEquals( "PoolMember", poolMemberId, "updatedAtBlockNumber", - memberUnitsUpdatedEvent.block.number.toString() + blockNumber.toString() ); }); }); From 9c888b7434943b61dd0cbd9f719dbc51269a2e9e Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 14:25:06 +0000 Subject: [PATCH 05/17] update dev container for matchstick compatibility --- .devcontainer/devcontainer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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. From 6adcdd4241fff63d8a377b5c572e0c107e19ee6f Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 14:45:52 +0000 Subject: [PATCH 06/17] implement the fix --- packages/subgraph/src/mappingHelpers.ts | 12 +++--- packages/subgraph/src/mappings/gdav1.ts | 10 ++--- .../subgraph/src/mappings/superfluidPool.ts | 41 +++++++++---------- .../tests/gdav1/hol/gdav1.hol.test.ts | 10 ++--- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index a272e5d05b..82cf7a0cb5 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -536,7 +536,7 @@ export function updatePoolTotalAmountFlowedAndDistributed( return pool; } -export function getOrInitPoolMember( +export function getOrInitOrUpdatePoolMember( event: ethereum.Event, poolAddress: Address, poolMemberAddress: Address @@ -548,19 +548,19 @@ 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.account = poolMemberAddress.toHex(); poolMember.pool = poolAddress.toHex(); } - + poolMember.updatedAtTimestamp = event.block.timestamp; + poolMember.updatedAtBlockNumber = event.block.number; + return poolMember; } diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index 81381e7464..e5a74baf36 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -19,7 +19,7 @@ import { _createTokenStatisticLogEntity, getOrInitPool, getOrInitPoolDistributor, - getOrInitPoolMember, + getOrInitOrUpdatePoolMember, getOrInitTokenStatistic, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, @@ -84,7 +84,7 @@ export function handlePoolConnectionUpdated( event: PoolConnectionUpdated ): void { // Update Pool Member Entity - let poolMember = getOrInitPoolMember( + let poolMember = getOrInitOrUpdatePoolMember( event, event.params.pool, event.params.account @@ -162,6 +162,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 +175,6 @@ export function handlePoolConnectionUpdated( ); _createTokenStatisticLogEntity(event, event.params.token, eventName); - - // Create Event Entity - _createPoolConnectionUpdatedEntity(event, poolMember.id); } export function handleBufferAdjusted(event: BufferAdjusted): void { diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index b018d2377a..6e2eaeb233 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -8,7 +8,7 @@ import { _createAccountTokenSnapshotLogEntity, _createTokenStatisticLogEntity, getOrInitPool, - getOrInitPoolMember, + getOrInitOrUpdatePoolMember, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, updatePoolMemberTotalAmountUntilUpdatedAtFields, @@ -28,7 +28,7 @@ export function handleDistributionClaimed(event: DistributionClaimed): void { pool.save(); // Update PoolMember - let poolMember = getOrInitPoolMember(event, event.address, event.params.member); + let poolMember = getOrInitOrUpdatePoolMember(event, event.address, event.params.member); poolMember.totalAmountClaimed = event.params.totalClaimed; poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); @@ -48,29 +48,17 @@ 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()); - - const previousUnits = poolMember.units; - const unitsDelta = event.params.newUnits.minus(previousUnits); + let poolMember = getOrInitOrUpdatePoolMember(event, event.address, event.params.member); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); - + + const hasMembershipWithUnits = membershipWithUnitsExists(poolMember.id); + const previousUnits = poolMember.units; + const unitsDelta = event.params.newUnits.minus(previousUnits); poolMember.units = event.params.newUnits; - 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); - if (poolMember.isConnected) { pool.totalConnectedUnits = pool.totalConnectedUnits.plus(unitsDelta); } else { @@ -79,7 +67,8 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { 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) { @@ -102,8 +91,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) { @@ -132,6 +123,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/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) From 527be2b0392d85f9b3ca73094e2b34c09db986fe Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 6 Mar 2024 14:54:48 +0000 Subject: [PATCH 07/17] clean-up --- packages/subgraph/src/mappings/superfluidPool.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index 6e2eaeb233..15a22f6b8c 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -54,7 +54,6 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); - const hasMembershipWithUnits = membershipWithUnitsExists(poolMember.id); const previousUnits = poolMember.units; const unitsDelta = event.params.newUnits.minus(previousUnits); poolMember.units = event.params.newUnits; @@ -81,7 +80,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 @@ -107,7 +106,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 From 599247eda0983ea4be162927594a0be1ecdc2715 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 7 Mar 2024 08:18:08 +0000 Subject: [PATCH 08/17] add elaborate test for pool total amount received --- ...-pool-member-total-amount-received.test.ts | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts 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..5527ea417b --- /dev/null +++ b/packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts @@ -0,0 +1,236 @@ +import { assert, describe, test } from "matchstick-as"; +import { Pool, PoolDistributor, PoolMember } from "../../generated/schema" +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { FAKE_INITIAL_BALANCE, alice as alice_, bob as bob_, charlie, delta, echo, maticXAddress, superfluidPool } from "../constants"; +import { BIG_INT_ZERO, getPoolMemberID } from "../../src/utils"; +import { handleInstantDistributionUpdated } from "../../src/mappings/gdav1"; +import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; +import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions"; +import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; + +describe("PoolMember not updating when units changed", () => { + test("emit MemberUnitsUpdated event", () => { + const superTokenAddress = maticXAddress; + + // # Arrange Pool + const poolAddress = Address.fromString(superfluidPool); + const poolAdminAndDistributorAddress = Address.fromString(delta); + let pool = new Pool(poolAddress.toHexString()); + pool.createdAtTimestamp = BigInt.fromI32(1); + pool.createdAtBlockNumber = BigInt.fromI32(1); + pool.updatedAtTimestamp = BigInt.fromI32(1); + pool.updatedAtBlockNumber = BigInt.fromI32(1); + + pool.totalMembers = 1; + pool.totalConnectedMembers = 1; + pool.totalDisconnectedMembers = 0; + pool.adjustmentFlowRate = BigInt.fromI32(0); + pool.flowRate = BigInt.fromI32(0); + pool.admin = poolAdminAndDistributorAddress.toHexString(); + pool.totalBuffer = BigInt.fromI32(0); + pool.token = superTokenAddress; + pool.totalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + pool.totalAmountFlowedDistributedUntilUpdatedAt = BigInt.fromI32(0); + pool.totalAmountInstantlyDistributedUntilUpdatedAt = BigInt.fromI32(0); + pool.totalConnectedUnits = BigInt.fromI32(1000); + pool.totalDisconnectedUnits = BigInt.fromI32(0); + pool.totalUnits = BigInt.fromI32(1000); + pool.save(); + // --- + + // # Arrange PoolMember 1 + const aliceAddress = Address.fromString(alice_); + const aliceId = getPoolMemberID(poolAddress, aliceAddress); + const alice = new PoolMember(aliceId) + alice.createdAtTimestamp = BigInt.fromI32(1); + alice.createdAtBlockNumber = BigInt.fromI32(1); + alice.updatedAtTimestamp = BigInt.fromI32(1); + alice.updatedAtBlockNumber = BigInt.fromI32(1); + + alice.account = aliceAddress.toHexString(); + alice.units = BigInt.fromI32(1000); + alice.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); + alice.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + alice.isConnected = true; + alice.totalAmountClaimed = BigInt.fromI32(0); + alice.pool = poolAddress.toHexString(); + alice.save(); + // # --- + + // # Arrange Distributor + const poolDistributor = new PoolDistributor(poolAdminAndDistributorAddress.toHexString()); + poolDistributor.createdAtTimestamp = BigInt.fromI32(1); + poolDistributor.createdAtBlockNumber = BigInt.fromI32(1); + poolDistributor.updatedAtTimestamp = BigInt.fromI32(1); + poolDistributor.updatedAtBlockNumber = BigInt.fromI32(1); + poolDistributor.account = charlie; + poolDistributor.totalBuffer = BigInt.fromI32(0); + poolDistributor.flowRate = BigInt.fromI32(0); + poolDistributor.pool = poolAddress.toHexString(); + poolDistributor.totalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt = BigInt.fromI32(0); + poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt = BigInt.fromI32(0); + poolDistributor.save(); + // --- + + // # First distribution + const instantDistributionEvent = createInstantDistributionUpdatedEvent( + superTokenAddress, + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(100), // requested amount + BigInt.fromI32(100), // actual amount + Bytes.fromHexString("0x") + ); + instantDistributionEvent.block.timestamp = BigInt.fromI32(1); + instantDistributionEvent.address = poolAddress; + + mockedGetAppManifest(poolAdminAndDistributorAddress.toHexString(), false, false, BIG_INT_ZERO); + mockedRealtimeBalanceOf( + superTokenAddress, + poolAdminAndDistributorAddress.toHexString(), + BigInt.fromI32(1), + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO + ); + + handleInstantDistributionUpdated(instantDistributionEvent); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "100" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "0" + ); + // # --- + + // # Arrange PoolMember 2 (new member) + const bobAddress = Address.fromString(bob_); + const bobId = getPoolMemberID(poolAddress, bobAddress); + const bob = new PoolMember(bobId) + bob.createdAtTimestamp = BigInt.fromI32(1); + bob.createdAtBlockNumber = BigInt.fromI32(1); + bob.updatedAtTimestamp = BigInt.fromI32(1); + bob.updatedAtBlockNumber = BigInt.fromI32(1); + + bob.account = bobAddress.toHexString(); + bob.units = BigInt.fromI32(1000); + bob.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); + bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + bob.isConnected = true; + bob.totalAmountClaimed = BigInt.fromI32(0); + bob.pool = poolAddress.toHexString(); + bob.save(); + // # --- + + // # Update Pool for member 2 + pool = Pool.load(poolAddress.toHexString())!; + pool.updatedAtTimestamp = BigInt.fromI32(2); + pool.updatedAtBlockNumber = BigInt.fromI32(2); + pool.totalMembers = 2; + pool.totalConnectedMembers = 2; + pool.totalDisconnectedMembers = 0; + pool.totalConnectedUnits = BigInt.fromI32(2000); + pool.totalUnits = BigInt.fromI32(2000); + pool.save(); + // --- + + // # Second distribution (we can use the first event again) + handleInstantDistributionUpdated(instantDistributionEvent); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "200" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "2000" + ); + // # --- + + // # Update PoolMember 2's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateBobUnitsEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(1000), // old units + BigInt.fromI32(1000) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateBobUnitsEvent.address = poolAddress; + updateBobUnitsEvent.block.timestamp = BigInt.fromI32(2); + + mockedGetAppManifest(bobAddress.toHexString(), false, false, BIG_INT_ZERO); + mockedRealtimeBalanceOf( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(2), + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO + ); + + handleMemberUnitsUpdated(updateBobUnitsEvent); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "200" + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "2000" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "50" + ); + + // # Update PoolMember 1's units to get the `totalAmountReceivedUntilUpdatedAt` + const updateAliceUnitsEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(10), // old units + BigInt.fromI32(10) // new units + ); + // Note, the units can stay the same, we just want to trigger an update. + updateAliceUnitsEvent.address = poolAddress; + updateAliceUnitsEvent.block.timestamp = BigInt.fromI32(3); + + mockedGetAppManifest(aliceAddress.toHexString(), false, false, BIG_INT_ZERO); + mockedRealtimeBalanceOf( + superTokenAddress, + aliceAddress.toHexString(), + BigInt.fromI32(3), + FAKE_INITIAL_BALANCE, + BigInt.fromI32(0), + BIG_INT_ZERO + ); + + handleMemberUnitsUpdated(updateAliceUnitsEvent); + + // assert.fieldEquals( + // "PoolMember", + // aliceId, + // "totalAmountReceivedUntilUpdatedAt", + // "150" + // ); + }) +}); + \ No newline at end of file From 62f8976c8a93d852fd1c607c58fb3623f14f35b7 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 7 Mar 2024 09:59:37 +0000 Subject: [PATCH 09/17] fix test issue --- ...3-07-pool-member-total-amount-received.test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 index 5527ea417b..2ed2d28b9f 100644 --- 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 @@ -124,7 +124,7 @@ describe("PoolMember not updating when units changed", () => { bob.account = bobAddress.toHexString(); bob.units = BigInt.fromI32(1000); bob.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); - bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(100); bob.isConnected = true; bob.totalAmountClaimed = BigInt.fromI32(0); bob.pool = poolAddress.toHexString(); @@ -225,12 +225,13 @@ describe("PoolMember not updating when units changed", () => { handleMemberUnitsUpdated(updateAliceUnitsEvent); - // assert.fieldEquals( - // "PoolMember", - // aliceId, - // "totalAmountReceivedUntilUpdatedAt", - // "150" - // ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "150" // 100 + ); + }) }); \ No newline at end of file From f7efd7cf0ef2ee382cc4e6d3067dd0c6b020f976 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 7 Mar 2024 10:07:00 +0000 Subject: [PATCH 10/17] add even more comments --- ...-pool-member-total-amount-received.test.ts | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) 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 index 2ed2d28b9f..d128c2c8a3 100644 --- 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 @@ -8,11 +8,28 @@ import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent } import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions"; import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; +/** + * Problem description + 1. Create pool + 2. Add member A and update A units to 100 + 3. Distribute 100 tokens + 4. Add member B and update B units to 100 + 4. Distribute 100 tokens + + Expected result: + member A 150 tokens + member B 50 tokens + + Actual result: + member A 100 tokens + member B 50 tokens + */ describe("PoolMember not updating when units changed", () => { test("emit MemberUnitsUpdated event", () => { const superTokenAddress = maticXAddress; - // # Arrange Pool + // # Arrange State 1 + // ## Arrange Pool const poolAddress = Address.fromString(superfluidPool); const poolAdminAndDistributorAddress = Address.fromString(delta); let pool = new Pool(poolAddress.toHexString()); @@ -32,13 +49,13 @@ describe("PoolMember not updating when units changed", () => { pool.totalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); pool.totalAmountFlowedDistributedUntilUpdatedAt = BigInt.fromI32(0); pool.totalAmountInstantlyDistributedUntilUpdatedAt = BigInt.fromI32(0); - pool.totalConnectedUnits = BigInt.fromI32(1000); + pool.totalConnectedUnits = BigInt.fromI32(100); pool.totalDisconnectedUnits = BigInt.fromI32(0); - pool.totalUnits = BigInt.fromI32(1000); + pool.totalUnits = BigInt.fromI32(100); pool.save(); // --- - // # Arrange PoolMember 1 + // ## Arrange PoolMember 1 const aliceAddress = Address.fromString(alice_); const aliceId = getPoolMemberID(poolAddress, aliceAddress); const alice = new PoolMember(aliceId) @@ -48,7 +65,7 @@ describe("PoolMember not updating when units changed", () => { alice.updatedAtBlockNumber = BigInt.fromI32(1); alice.account = aliceAddress.toHexString(); - alice.units = BigInt.fromI32(1000); + alice.units = BigInt.fromI32(100); alice.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); alice.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); alice.isConnected = true; @@ -57,7 +74,7 @@ describe("PoolMember not updating when units changed", () => { alice.save(); // # --- - // # Arrange Distributor + // ## Arrange Distributor const poolDistributor = new PoolDistributor(poolAdminAndDistributorAddress.toHexString()); poolDistributor.createdAtTimestamp = BigInt.fromI32(1); poolDistributor.createdAtBlockNumber = BigInt.fromI32(1); @@ -73,7 +90,7 @@ describe("PoolMember not updating when units changed", () => { poolDistributor.save(); // --- - // # First distribution + // # First distribution (State 2) const instantDistributionEvent = createInstantDistributionUpdatedEvent( superTokenAddress, poolAddress.toHexString(), @@ -112,7 +129,8 @@ describe("PoolMember not updating when units changed", () => { ); // # --- - // # Arrange PoolMember 2 (new member) + // # Arrange State 3 + // ## Arrange PoolMember 2 (new member) const bobAddress = Address.fromString(bob_); const bobId = getPoolMemberID(poolAddress, bobAddress); const bob = new PoolMember(bobId) @@ -122,7 +140,7 @@ describe("PoolMember not updating when units changed", () => { bob.updatedAtBlockNumber = BigInt.fromI32(1); bob.account = bobAddress.toHexString(); - bob.units = BigInt.fromI32(1000); + bob.units = BigInt.fromI32(100); bob.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(100); bob.isConnected = true; @@ -131,7 +149,7 @@ describe("PoolMember not updating when units changed", () => { bob.save(); // # --- - // # Update Pool for member 2 + // ## Update Pool for member 2 pool = Pool.load(poolAddress.toHexString())!; pool.updatedAtTimestamp = BigInt.fromI32(2); pool.updatedAtBlockNumber = BigInt.fromI32(2); @@ -139,11 +157,11 @@ describe("PoolMember not updating when units changed", () => { pool.totalConnectedMembers = 2; pool.totalDisconnectedMembers = 0; pool.totalConnectedUnits = BigInt.fromI32(2000); - pool.totalUnits = BigInt.fromI32(2000); + pool.totalUnits = BigInt.fromI32(200); pool.save(); // --- - // # Second distribution (we can use the first event again) + // # Second distribution (we can use the first event again) (State 4) handleInstantDistributionUpdated(instantDistributionEvent); assert.fieldEquals( @@ -156,7 +174,7 @@ describe("PoolMember not updating when units changed", () => { "Pool", poolAddress.toHexString(), "totalUnits", - "2000" + "200" ); // # --- @@ -164,8 +182,8 @@ describe("PoolMember not updating when units changed", () => { const updateBobUnitsEvent = createMemberUnitsUpdatedEvent( superTokenAddress, bobAddress.toHexString(), - BigInt.fromI32(1000), // old units - BigInt.fromI32(1000) // new units + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units ); // Note, the units can stay the same, we just want to trigger an update. updateBobUnitsEvent.address = poolAddress; @@ -181,6 +199,7 @@ describe("PoolMember not updating when units changed", () => { BIG_INT_ZERO ); + // Act 1 handleMemberUnitsUpdated(updateBobUnitsEvent); assert.fieldEquals( @@ -193,7 +212,7 @@ describe("PoolMember not updating when units changed", () => { "Pool", poolAddress.toHexString(), "totalUnits", - "2000" + "200" ); assert.fieldEquals( "PoolMember", @@ -223,15 +242,15 @@ describe("PoolMember not updating when units changed", () => { BIG_INT_ZERO ); + // Act 2 handleMemberUnitsUpdated(updateAliceUnitsEvent); assert.fieldEquals( "PoolMember", aliceId, "totalAmountReceivedUntilUpdatedAt", - "150" // 100 + "150" // 100 from first + 50 from second ); - }) }); \ No newline at end of file From 76a333afa263c69496c585a90911e0326d6d4423 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 7 Mar 2024 10:08:29 +0000 Subject: [PATCH 11/17] fix test name --- .../bugs/2024-03-07-pool-member-total-amount-received.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index d128c2c8a3..53f97cbde2 100644 --- 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 @@ -24,8 +24,8 @@ import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; member A 100 tokens member B 50 tokens */ -describe("PoolMember not updating when units changed", () => { - test("emit MemberUnitsUpdated event", () => { +describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", () => { + test("create elaborate scenario with 2 distributions and 2 pool members", () => { const superTokenAddress = maticXAddress; // # Arrange State 1 From aa9dd7fd2ae24767ad85b9a7d840c0b38748058e Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 12:39:11 +0200 Subject: [PATCH 12/17] per unit semantic money logic copy to subgraph --- packages/subgraph/schema.graphql | 8 ++ packages/subgraph/src/mappingHelpers.ts | 104 +++++++++++++----- packages/subgraph/src/mappings/gdav1.ts | 23 +++- .../subgraph/src/mappings/superfluidPool.ts | 33 ++++-- 4 files changed, 131 insertions(+), 37 deletions(-) diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 1627c06aa4..cd8d466220 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -1757,6 +1757,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 +1817,9 @@ type PoolMember @entity { poolTotalAmountDistributedUntilUpdatedAt: BigInt! totalAmountReceivedUntilUpdatedAt: BigInt! + syncedPerUnitSettledValue: BigInt! + syncedPerUnitFlowRate: BigInt! + account: Account! pool: Pool! diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index a272e5d05b..7c99203ac2 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -499,6 +499,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; @@ -557,6 +562,9 @@ export function getOrInitPoolMember( 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(); } @@ -1471,33 +1479,79 @@ 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/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index 81381e7464..925531ea61 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -21,10 +21,11 @@ import { getOrInitPoolDistributor, getOrInitPoolMember, getOrInitTokenStatistic, + settlePDPoolMemberMU, + settlePoolParticle, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, updatePoolDistributorTotalAmountFlowedAndDistributed, - updatePoolMemberTotalAmountUntilUpdatedAtFields, updatePoolTotalAmountFlowedAndDistributed, updateSenderATSStreamData, updateTokenStatisticStreamData, @@ -98,6 +99,8 @@ 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 + settlePDPoolMemberMU(pool, poolMember, event.block); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); if (poolMember.units.gt(BIG_INT_ZERO)) { if (memberConnectedStatusUpdated) { @@ -126,9 +129,6 @@ export function handlePoolConnectionUpdated( } } } - - // Update totalAmountDistributedUntilUpdatedAt - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); pool.save(); poolMember.save(); @@ -229,7 +229,14 @@ 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 = event.params.newDistributorToPoolFlowRate.div( + pool.totalUnits + ); pool.flowRate = event.params.newTotalDistributionFlowRate; pool.adjustmentFlowRate = event.params.adjustmentFlowRate; pool.save(); @@ -312,7 +319,15 @@ 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( + event.params.actualAmount.div(pool.totalUnits) + ); const previousTotalAmountDistributed = pool.totalAmountDistributedUntilUpdatedAt; pool.totalAmountInstantlyDistributedUntilUpdatedAt = diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index b018d2377a..ffee85281b 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -9,9 +9,9 @@ import { _createTokenStatisticLogEntity, getOrInitPool, getOrInitPoolMember, + settlePDPoolMemberMU, updateATSStreamedAndBalanceUntilUpdatedAt, updateAggregateDistributionAgreementData, - updatePoolMemberTotalAmountUntilUpdatedAtFields, updatePoolTotalAmountFlowedAndDistributed, updateTokenStatsStreamedUntilUpdatedAt, } from "../mappingHelpers"; @@ -24,14 +24,16 @@ export function handleDistributionClaimed(event: DistributionClaimed): void { // Update Pool let pool = getOrInitPool(event, event.address.toHex()); - pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); - pool.save(); + let poolMember = getOrInitPoolMember(event, event.address, event.params.member); + // settle pool and pool member + settlePDPoolMemberMU(pool, poolMember, event.block); + pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + // 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 @@ -57,11 +59,27 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { const previousUnits = poolMember.units; const unitsDelta = event.params.newUnits.minus(previousUnits); + const newTotalUnits = pool.totalUnits.plus(unitsDelta); + settlePDPoolMemberMU(pool, poolMember, event.block); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); - poolMember = updatePoolMemberTotalAmountUntilUpdatedAtFields(pool, poolMember); + // @note TODO update the pool.perUnitFlowRate + // @note TODO update the poolMember.perUnitFlowRate + const existingPoolFlowRate = pool.perUnitFlowRate.times(pool.totalUnits); + let newPerUnitFlowRate; + let remainderRate; + 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; + poolMember.syncedPerUnitFlowRate = poolMember.syncedPerUnitFlowRate.plus(remainderRate); poolMember.units = event.params.newUnits; const eventName = "MemberUnitsUpdated"; @@ -76,7 +94,6 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { } 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)) { From d224d2edf544c27e18aed2d647b70ff646d6ddee Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 12 Mar 2024 13:32:54 +0200 Subject: [PATCH 13/17] fix up test case --- packages/subgraph/src/mappingHelpers.ts | 5 + packages/subgraph/src/mappings/gdav1.ts | 9 +- .../subgraph/src/mappings/superfluidPool.ts | 7 +- packages/subgraph/src/utils.ts | 9 ++ ...-pool-member-total-amount-received.test.ts | 140 +++++++++++------- 5 files changed, 109 insertions(+), 61 deletions(-) diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index 3f5e3c803d..ee64258e82 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -46,6 +46,7 @@ import { getResolverAddress, } from "./addresses"; import { FlowUpdated } from "../generated/ConstantFlowAgreementV1/IConstantFlowAgreementV1"; +import { log } from "matchstick-as"; /************************************************************************** * HOL initializer functions @@ -1503,6 +1504,9 @@ export function monetaryUnitPoolMemberRTB(pool: Pool, poolMember: PoolMember, cu currentTimestamp, poolMember.updatedAtTimestamp ); + log.debug("poolPerUnitRTB {}", [poolPerUnitRTB.toString()]); + log.debug("poolMemberPerUnitRTB {}", [poolMemberPerUnitRTB.toString()]); + log.debug("poolMember.units {}", [poolMember.units.toString()]); return poolMember.totalAmountReceivedUntilUpdatedAt.plus( poolPerUnitRTB.minus(poolMemberPerUnitRTB).times(poolMember.units) ); @@ -1551,6 +1555,7 @@ export function syncPoolMemberParticle(pool: Pool, poolMember: PoolMember): Pool } export function settlePDPoolMemberMU(pool: Pool, poolMember: PoolMember, block: ethereum.Block): void { + log.debug("pool.perUnitSettledValue {}", [pool.perUnitSettledValue.toString()]); pool = settlePoolParticle(pool, block); poolMember.totalAmountReceivedUntilUpdatedAt = monetaryUnitPoolMemberRTB(pool, poolMember, block.timestamp); poolMember = syncPoolMemberParticle(pool, poolMember); diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index e826979559..9c2cd3540c 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -34,6 +34,7 @@ import { import { BIG_INT_ZERO, createEventID, + divideOrZero, initializeEventEntity, membershipWithUnitsExists, } from "../utils"; @@ -234,9 +235,7 @@ export function handleFlowDistributionUpdated( // in the two functions pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); pool = settlePoolParticle(pool, event.block); - pool.perUnitFlowRate = event.params.newDistributorToPoolFlowRate.div( - pool.totalUnits - ); + pool.perUnitFlowRate = divideOrZero(event.params.newDistributorToPoolFlowRate, pool.totalUnits); pool.flowRate = event.params.newTotalDistributionFlowRate; pool.adjustmentFlowRate = event.params.adjustmentFlowRate; pool.save(); @@ -325,9 +324,7 @@ export function handleInstantDistributionUpdated( pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); pool = settlePoolParticle(pool, event.block); // @note a speculations on what needs to be done - pool.perUnitSettledValue = pool.perUnitSettledValue.plus( - event.params.actualAmount.div(pool.totalUnits) - ); + pool.perUnitSettledValue = pool.perUnitSettledValue.plus(divideOrZero(event.params.actualAmount, pool.totalUnits)); const previousTotalAmountDistributed = pool.totalAmountDistributedUntilUpdatedAt; pool.totalAmountInstantlyDistributedUntilUpdatedAt = diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index 1a49b6c7da..2ba1c89077 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -63,9 +63,10 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { // @note TODO update the pool.perUnitFlowRate // @note TODO update the poolMember.perUnitFlowRate - const existingPoolFlowRate = pool.perUnitFlowRate.times(pool.totalUnits); - let newPerUnitFlowRate; - let remainderRate; + const existingPoolFlowRate = pool.perUnitFlowRate.times(pool.totalUnits); + let newPerUnitFlowRate: BigInt; + let remainderRate: BigInt; + if (!newTotalUnits.equals(BIG_INT_ZERO)) { newPerUnitFlowRate = existingPoolFlowRate.div(newTotalUnits); remainderRate = existingPoolFlowRate.minus(newPerUnitFlowRate.times(newTotalUnits)); diff --git a/packages/subgraph/src/utils.ts b/packages/subgraph/src/utils.ts index a182123f3c..ce7d5c1ed6 100644 --- a/packages/subgraph/src/utils.ts +++ b/packages/subgraph/src/utils.ts @@ -387,3 +387,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-03-07-pool-member-total-amount-received.test.ts b/packages/subgraph/tests/bugs/2024-03-07-pool-member-total-amount-received.test.ts index 53f97cbde2..620924c1b8 100644 --- 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 @@ -1,8 +1,8 @@ -import { assert, describe, test } from "matchstick-as"; +import { assert, describe, log, test } from "matchstick-as"; import { Pool, PoolDistributor, PoolMember } from "../../generated/schema" import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; import { FAKE_INITIAL_BALANCE, alice as alice_, bob as bob_, charlie, delta, echo, maticXAddress, superfluidPool } from "../constants"; -import { BIG_INT_ZERO, getPoolMemberID } from "../../src/utils"; +import { BIG_INT_ONE, BIG_INT_ZERO, getPoolMemberID } from "../../src/utils"; import { handleInstantDistributionUpdated } from "../../src/mappings/gdav1"; import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions"; @@ -33,24 +33,27 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", const poolAddress = Address.fromString(superfluidPool); const poolAdminAndDistributorAddress = Address.fromString(delta); let pool = new Pool(poolAddress.toHexString()); - pool.createdAtTimestamp = BigInt.fromI32(1); - pool.createdAtBlockNumber = BigInt.fromI32(1); - pool.updatedAtTimestamp = BigInt.fromI32(1); - pool.updatedAtBlockNumber = BigInt.fromI32(1); + pool.createdAtTimestamp = BIG_INT_ONE; + pool.createdAtBlockNumber = BIG_INT_ONE; + pool.updatedAtTimestamp = BIG_INT_ONE; + pool.updatedAtBlockNumber = BIG_INT_ONE; pool.totalMembers = 1; pool.totalConnectedMembers = 1; pool.totalDisconnectedMembers = 0; - pool.adjustmentFlowRate = BigInt.fromI32(0); - pool.flowRate = BigInt.fromI32(0); + pool.adjustmentFlowRate = BIG_INT_ZERO; + pool.flowRate = BIG_INT_ZERO; pool.admin = poolAdminAndDistributorAddress.toHexString(); - pool.totalBuffer = BigInt.fromI32(0); + pool.totalBuffer = BIG_INT_ZERO; pool.token = superTokenAddress; - pool.totalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); - pool.totalAmountFlowedDistributedUntilUpdatedAt = BigInt.fromI32(0); - pool.totalAmountInstantlyDistributedUntilUpdatedAt = BigInt.fromI32(0); + pool.perUnitFlowRate = BIG_INT_ZERO; + pool.perUnitSettledValue = BIG_INT_ZERO; + pool.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; + pool.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO; + pool.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO; + pool.totalFlowAdjustmentAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; pool.totalConnectedUnits = BigInt.fromI32(100); - pool.totalDisconnectedUnits = BigInt.fromI32(0); + pool.totalDisconnectedUnits = BIG_INT_ZERO; pool.totalUnits = BigInt.fromI32(100); pool.save(); // --- @@ -59,34 +62,36 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", const aliceAddress = Address.fromString(alice_); const aliceId = getPoolMemberID(poolAddress, aliceAddress); const alice = new PoolMember(aliceId) - alice.createdAtTimestamp = BigInt.fromI32(1); - alice.createdAtBlockNumber = BigInt.fromI32(1); - alice.updatedAtTimestamp = BigInt.fromI32(1); - alice.updatedAtBlockNumber = BigInt.fromI32(1); + alice.createdAtTimestamp = BIG_INT_ONE; + alice.createdAtBlockNumber = BIG_INT_ONE; + alice.updatedAtTimestamp = BIG_INT_ONE; + alice.updatedAtBlockNumber = BIG_INT_ONE; alice.account = aliceAddress.toHexString(); alice.units = BigInt.fromI32(100); - alice.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); - alice.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); + alice.totalAmountReceivedUntilUpdatedAt = BIG_INT_ZERO; + alice.poolTotalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; + alice.syncedPerUnitFlowRate = BIG_INT_ZERO; + alice.syncedPerUnitSettledValue = BIG_INT_ZERO; alice.isConnected = true; - alice.totalAmountClaimed = BigInt.fromI32(0); + alice.totalAmountClaimed = BIG_INT_ZERO; alice.pool = poolAddress.toHexString(); alice.save(); // # --- // ## Arrange Distributor const poolDistributor = new PoolDistributor(poolAdminAndDistributorAddress.toHexString()); - poolDistributor.createdAtTimestamp = BigInt.fromI32(1); - poolDistributor.createdAtBlockNumber = BigInt.fromI32(1); - poolDistributor.updatedAtTimestamp = BigInt.fromI32(1); - poolDistributor.updatedAtBlockNumber = BigInt.fromI32(1); + poolDistributor.createdAtTimestamp = BIG_INT_ONE; + poolDistributor.createdAtBlockNumber = BIG_INT_ONE; + poolDistributor.updatedAtTimestamp = BIG_INT_ONE; + poolDistributor.updatedAtBlockNumber = BIG_INT_ONE; poolDistributor.account = charlie; - poolDistributor.totalBuffer = BigInt.fromI32(0); - poolDistributor.flowRate = BigInt.fromI32(0); + poolDistributor.totalBuffer = BIG_INT_ZERO; + poolDistributor.flowRate = BIG_INT_ZERO; poolDistributor.pool = poolAddress.toHexString(); - poolDistributor.totalAmountDistributedUntilUpdatedAt = BigInt.fromI32(0); - poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt = BigInt.fromI32(0); - poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt = BigInt.fromI32(0); + poolDistributor.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; + poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO; + poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO; poolDistributor.save(); // --- @@ -100,16 +105,16 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", BigInt.fromI32(100), // actual amount Bytes.fromHexString("0x") ); - instantDistributionEvent.block.timestamp = BigInt.fromI32(1); + instantDistributionEvent.block.timestamp = BIG_INT_ONE; instantDistributionEvent.address = poolAddress; mockedGetAppManifest(poolAdminAndDistributorAddress.toHexString(), false, false, BIG_INT_ZERO); mockedRealtimeBalanceOf( superTokenAddress, poolAdminAndDistributorAddress.toHexString(), - BigInt.fromI32(1), + BIG_INT_ONE, FAKE_INITIAL_BALANCE, - BigInt.fromI32(0), + BIG_INT_ZERO, BIG_INT_ZERO ); @@ -129,24 +134,54 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", ); // # --- - // # Arrange State 3 - // ## Arrange PoolMember 2 (new member) const bobAddress = Address.fromString(bob_); const bobId = getPoolMemberID(poolAddress, bobAddress); - const bob = new PoolMember(bobId) - bob.createdAtTimestamp = BigInt.fromI32(1); - bob.createdAtBlockNumber = BigInt.fromI32(1); - bob.updatedAtTimestamp = BigInt.fromI32(1); - bob.updatedAtBlockNumber = BigInt.fromI32(1); + let updateBobUnitsEvent = createMemberUnitsUpdatedEvent( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units + ); - bob.account = bobAddress.toHexString(); - bob.units = BigInt.fromI32(100); - bob.totalAmountReceivedUntilUpdatedAt = BigInt.fromI32(0); - bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(100); - bob.isConnected = true; - bob.totalAmountClaimed = BigInt.fromI32(0); - bob.pool = poolAddress.toHexString(); - bob.save(); + updateBobUnitsEvent.address = poolAddress; + updateBobUnitsEvent.block.timestamp = BigInt.fromI32(2); + + mockedGetAppManifest(bobAddress.toHexString(), false, false, BIG_INT_ZERO); + mockedRealtimeBalanceOf( + superTokenAddress, + bobAddress.toHexString(), + BigInt.fromI32(2), + FAKE_INITIAL_BALANCE, + BIG_INT_ZERO, + BIG_INT_ZERO + ); + + handleMemberUnitsUpdated(updateBobUnitsEvent); + // Note, the units can stay the same, we just want to trigger an update. + + // # Arrange State 3 + // ## Arrange PoolMember 2 (new member) + // const bobId = getPoolMemberID(poolAddress, bobAddress); + // const bob = new PoolMember(bobId) + // bob.createdAtTimestamp = BIG_INT_ONE; + // bob.createdAtBlockNumber = BIG_INT_ONE; + // bob.updatedAtTimestamp = BIG_INT_ONE; + // bob.updatedAtBlockNumber = BIG_INT_ONE; + + // bob.account = bobAddress.toHexString(); + // bob.units = BigInt.fromI32(100); + // bob.totalAmountReceivedUntilUpdatedAt = BIG_INT_ZERO; + // bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(100); + // bob.isConnected = true; + // bob.totalAmountClaimed = BIG_INT_ZERO; + // bob.pool = poolAddress.toHexString(); + + // // get pool to use the synced per unit flow rate and settled value + // pool = Pool.load(poolAddress.toHexString())!; + // bob.syncedPerUnitFlowRate = pool.perUnitFlowRate; + // bob.syncedPerUnitSettledValue = pool.perUnitSettledValue; + + // bob.save(); // # --- // ## Update Pool for member 2 @@ -156,7 +191,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", pool.totalMembers = 2; pool.totalConnectedMembers = 2; pool.totalDisconnectedMembers = 0; - pool.totalConnectedUnits = BigInt.fromI32(2000); + pool.totalConnectedUnits = BigInt.fromI32(200); pool.totalUnits = BigInt.fromI32(200); pool.save(); // --- @@ -179,7 +214,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", // # --- // # Update PoolMember 2's units to get the `totalAmountReceivedUntilUpdatedAt` - const updateBobUnitsEvent = createMemberUnitsUpdatedEvent( + updateBobUnitsEvent = createMemberUnitsUpdatedEvent( superTokenAddress, bobAddress.toHexString(), BigInt.fromI32(100), // old units @@ -195,13 +230,14 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", bobAddress.toHexString(), BigInt.fromI32(2), FAKE_INITIAL_BALANCE, - BigInt.fromI32(0), + BIG_INT_ZERO, BIG_INT_ZERO ); // Act 1 + log.debug("Act 1", []); handleMemberUnitsUpdated(updateBobUnitsEvent); - + assert.fieldEquals( "Pool", poolAddress.toHexString(), @@ -238,7 +274,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", aliceAddress.toHexString(), BigInt.fromI32(3), FAKE_INITIAL_BALANCE, - BigInt.fromI32(0), + BIG_INT_ZERO, BIG_INT_ZERO ); From 726a14b996c8430aca60e9987d4e866b93e3ed1a Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 13 Mar 2024 09:28:21 +0000 Subject: [PATCH 14/17] clean-up the test --- ...-pool-member-total-amount-received.test.ts | 287 +++++++----------- packages/subgraph/tests/mockedFunctions.ts | 16 + 2 files changed, 120 insertions(+), 183 deletions(-) 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 index 620924c1b8..4c9160e236 100644 --- 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 @@ -1,11 +1,10 @@ -import { assert, describe, log, test } from "matchstick-as"; -import { Pool, PoolDistributor, PoolMember } from "../../generated/schema" +import { assert, describe, test } from "matchstick-as"; import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts"; -import { FAKE_INITIAL_BALANCE, alice as alice_, bob as bob_, charlie, delta, echo, maticXAddress, superfluidPool } from "../constants"; -import { BIG_INT_ONE, BIG_INT_ZERO, getPoolMemberID } from "../../src/utils"; +import { alice as alice_, bob as bob_, delta, echo, maticXAddress, superfluidPool } from "../constants"; +import { getPoolMemberID } from "../../src/utils"; import { handleInstantDistributionUpdated } from "../../src/mappings/gdav1"; -import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent } from "../gdav1/gdav1.helper"; -import { mockedGetAppManifest, mockedRealtimeBalanceOf } from "../mockedFunctions"; +import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent, createPoolAndReturnPoolCreatedEvent } from "../gdav1/gdav1.helper"; +import { mockedAppManifestAndRealtimeBalanceOf } from "../mockedFunctions"; import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; /** @@ -27,76 +26,30 @@ import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", () => { test("create elaborate scenario with 2 distributions and 2 pool members", () => { const superTokenAddress = maticXAddress; - + const poolAdminAndDistributorAddress = Address.fromString(delta); + const poolAddress = Address.fromString(superfluidPool); + // # Arrange State 1 // ## Arrange Pool - const poolAddress = Address.fromString(superfluidPool); - const poolAdminAndDistributorAddress = Address.fromString(delta); - let pool = new Pool(poolAddress.toHexString()); - pool.createdAtTimestamp = BIG_INT_ONE; - pool.createdAtBlockNumber = BIG_INT_ONE; - pool.updatedAtTimestamp = BIG_INT_ONE; - pool.updatedAtBlockNumber = BIG_INT_ONE; + const poolCreatedEvent = createPoolAndReturnPoolCreatedEvent(poolAdminAndDistributorAddress.toHexString(), superTokenAddress, poolAddress.toHexString()); - pool.totalMembers = 1; - pool.totalConnectedMembers = 1; - pool.totalDisconnectedMembers = 0; - pool.adjustmentFlowRate = BIG_INT_ZERO; - pool.flowRate = BIG_INT_ZERO; - pool.admin = poolAdminAndDistributorAddress.toHexString(); - pool.totalBuffer = BIG_INT_ZERO; - pool.token = superTokenAddress; - pool.perUnitFlowRate = BIG_INT_ZERO; - pool.perUnitSettledValue = BIG_INT_ZERO; - pool.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; - pool.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO; - pool.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO; - pool.totalFlowAdjustmentAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; - pool.totalConnectedUnits = BigInt.fromI32(100); - pool.totalDisconnectedUnits = BIG_INT_ZERO; - pool.totalUnits = BigInt.fromI32(100); - pool.save(); - // --- - // ## Arrange PoolMember 1 const aliceAddress = Address.fromString(alice_); const aliceId = getPoolMemberID(poolAddress, aliceAddress); - const alice = new PoolMember(aliceId) - alice.createdAtTimestamp = BIG_INT_ONE; - alice.createdAtBlockNumber = BIG_INT_ONE; - alice.updatedAtTimestamp = BIG_INT_ONE; - alice.updatedAtBlockNumber = BIG_INT_ONE; + 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; - alice.account = aliceAddress.toHexString(); - alice.units = BigInt.fromI32(100); - alice.totalAmountReceivedUntilUpdatedAt = BIG_INT_ZERO; - alice.poolTotalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; - alice.syncedPerUnitFlowRate = BIG_INT_ZERO; - alice.syncedPerUnitSettledValue = BIG_INT_ZERO; - alice.isConnected = true; - alice.totalAmountClaimed = BIG_INT_ZERO; - alice.pool = poolAddress.toHexString(); - alice.save(); - // # --- + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), aliceCreatedEvent.block.timestamp); + handleMemberUnitsUpdated(aliceCreatedEvent); - // ## Arrange Distributor - const poolDistributor = new PoolDistributor(poolAdminAndDistributorAddress.toHexString()); - poolDistributor.createdAtTimestamp = BIG_INT_ONE; - poolDistributor.createdAtBlockNumber = BIG_INT_ONE; - poolDistributor.updatedAtTimestamp = BIG_INT_ONE; - poolDistributor.updatedAtBlockNumber = BIG_INT_ONE; - poolDistributor.account = charlie; - poolDistributor.totalBuffer = BIG_INT_ZERO; - poolDistributor.flowRate = BIG_INT_ZERO; - poolDistributor.pool = poolAddress.toHexString(); - poolDistributor.totalAmountDistributedUntilUpdatedAt = BIG_INT_ZERO; - poolDistributor.totalAmountFlowedDistributedUntilUpdatedAt = BIG_INT_ZERO; - poolDistributor.totalAmountInstantlyDistributedUntilUpdatedAt = BIG_INT_ZERO; - poolDistributor.save(); - // --- - - // # First distribution (State 2) - const instantDistributionEvent = createInstantDistributionUpdatedEvent( + // # First distribution + const firstDistributionEvent = createInstantDistributionUpdatedEvent( superTokenAddress, poolAddress.toHexString(), poolAdminAndDistributorAddress.toHexString(), @@ -105,20 +58,11 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", BigInt.fromI32(100), // actual amount Bytes.fromHexString("0x") ); - instantDistributionEvent.block.timestamp = BIG_INT_ONE; - instantDistributionEvent.address = poolAddress; - - mockedGetAppManifest(poolAdminAndDistributorAddress.toHexString(), false, false, BIG_INT_ZERO); - mockedRealtimeBalanceOf( - superTokenAddress, - poolAdminAndDistributorAddress.toHexString(), - BIG_INT_ONE, - FAKE_INITIAL_BALANCE, - BIG_INT_ZERO, - BIG_INT_ZERO - ); + firstDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; + firstDistributionEvent.address = poolAddress; - handleInstantDistributionUpdated(instantDistributionEvent); + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), firstDistributionEvent.block.timestamp); + handleInstantDistributionUpdated(firstDistributionEvent); assert.fieldEquals( "Pool", @@ -126,79 +70,64 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "totalAmountDistributedUntilUpdatedAt", "100" ); + 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 updateBobUnitsEvent = createMemberUnitsUpdatedEvent( + let createBobEvent = createMemberUnitsUpdatedEvent( superTokenAddress, bobAddress.toHexString(), - BigInt.fromI32(100), // old units + BigInt.fromI32(0), // old units BigInt.fromI32(100) // new units ); + createBobEvent.address = poolAddress; + createBobEvent.block.timestamp = BigInt.fromI32(2); - updateBobUnitsEvent.address = poolAddress; - updateBobUnitsEvent.block.timestamp = BigInt.fromI32(2); + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), createBobEvent.block.timestamp); + handleMemberUnitsUpdated(createBobEvent); - mockedGetAppManifest(bobAddress.toHexString(), false, false, BIG_INT_ZERO); - mockedRealtimeBalanceOf( + // # Second distribution + const secondDistributionEvent = createInstantDistributionUpdatedEvent( superTokenAddress, - bobAddress.toHexString(), - BigInt.fromI32(2), - FAKE_INITIAL_BALANCE, - BIG_INT_ZERO, - BIG_INT_ZERO + poolAddress.toHexString(), + poolAdminAndDistributorAddress.toHexString(), + echo, + BigInt.fromI32(100), // requested amount + BigInt.fromI32(100), // actual amount + Bytes.fromHexString("0x") ); + secondDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; + secondDistributionEvent.address = poolAddress; + + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), secondDistributionEvent.block.timestamp); + handleInstantDistributionUpdated(secondDistributionEvent); - handleMemberUnitsUpdated(updateBobUnitsEvent); - // Note, the units can stay the same, we just want to trigger an update. - - // # Arrange State 3 - // ## Arrange PoolMember 2 (new member) - // const bobId = getPoolMemberID(poolAddress, bobAddress); - // const bob = new PoolMember(bobId) - // bob.createdAtTimestamp = BIG_INT_ONE; - // bob.createdAtBlockNumber = BIG_INT_ONE; - // bob.updatedAtTimestamp = BIG_INT_ONE; - // bob.updatedAtBlockNumber = BIG_INT_ONE; - - // bob.account = bobAddress.toHexString(); - // bob.units = BigInt.fromI32(100); - // bob.totalAmountReceivedUntilUpdatedAt = BIG_INT_ZERO; - // bob.poolTotalAmountDistributedUntilUpdatedAt = BigInt.fromI32(100); - // bob.isConnected = true; - // bob.totalAmountClaimed = BIG_INT_ZERO; - // bob.pool = poolAddress.toHexString(); - - // // get pool to use the synced per unit flow rate and settled value - // pool = Pool.load(poolAddress.toHexString())!; - // bob.syncedPerUnitFlowRate = pool.perUnitFlowRate; - // bob.syncedPerUnitSettledValue = pool.perUnitSettledValue; - - // bob.save(); - // # --- - - // ## Update Pool for member 2 - pool = Pool.load(poolAddress.toHexString())!; - pool.updatedAtTimestamp = BigInt.fromI32(2); - pool.updatedAtBlockNumber = BigInt.fromI32(2); - pool.totalMembers = 2; - pool.totalConnectedMembers = 2; - pool.totalDisconnectedMembers = 0; - pool.totalConnectedUnits = BigInt.fromI32(200); - pool.totalUnits = BigInt.fromI32(200); - pool.save(); - // --- - - // # Second distribution (we can use the first event again) (State 4) - handleInstantDistributionUpdated(instantDistributionEvent); - assert.fieldEquals( "Pool", poolAddress.toHexString(), @@ -211,81 +140,73 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "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` - updateBobUnitsEvent = createMemberUnitsUpdatedEvent( + 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. - updateBobUnitsEvent.address = poolAddress; - updateBobUnitsEvent.block.timestamp = BigInt.fromI32(2); + updateBobEvent.address = poolAddress; + updateBobEvent.block.timestamp = BigInt.fromI32(3); - mockedGetAppManifest(bobAddress.toHexString(), false, false, BIG_INT_ZERO); - mockedRealtimeBalanceOf( - superTokenAddress, - bobAddress.toHexString(), - BigInt.fromI32(2), - FAKE_INITIAL_BALANCE, - BIG_INT_ZERO, - BIG_INT_ZERO - ); + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), updateBobEvent.block.timestamp); + handleMemberUnitsUpdated(createBobEvent); - // Act 1 - log.debug("Act 1", []); - handleMemberUnitsUpdated(updateBobUnitsEvent); - - assert.fieldEquals( - "Pool", - poolAddress.toHexString(), - "totalAmountDistributedUntilUpdatedAt", - "200" - ); - assert.fieldEquals( - "Pool", - poolAddress.toHexString(), - "totalUnits", - "200" - ); assert.fieldEquals( "PoolMember", bobId, "totalAmountReceivedUntilUpdatedAt", "50" ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); // # Update PoolMember 1's units to get the `totalAmountReceivedUntilUpdatedAt` - const updateAliceUnitsEvent = createMemberUnitsUpdatedEvent( + const updateAliceEvent = createMemberUnitsUpdatedEvent( superTokenAddress, aliceAddress.toHexString(), - BigInt.fromI32(10), // old units - BigInt.fromI32(10) // new units + BigInt.fromI32(100), // old units + BigInt.fromI32(100) // new units ); // Note, the units can stay the same, we just want to trigger an update. - updateAliceUnitsEvent.address = poolAddress; - updateAliceUnitsEvent.block.timestamp = BigInt.fromI32(3); - - mockedGetAppManifest(aliceAddress.toHexString(), false, false, BIG_INT_ZERO); - mockedRealtimeBalanceOf( - superTokenAddress, - aliceAddress.toHexString(), - BigInt.fromI32(3), - FAKE_INITIAL_BALANCE, - BIG_INT_ZERO, - BIG_INT_ZERO - ); + updateAliceEvent.address = poolAddress; + updateAliceEvent.block.timestamp = BigInt.fromI32(3); - // Act 2 - handleMemberUnitsUpdated(updateAliceUnitsEvent); + mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), updateAliceEvent.block.timestamp); + handleMemberUnitsUpdated(updateAliceEvent); assert.fieldEquals( "PoolMember", aliceId, "totalAmountReceivedUntilUpdatedAt", - "150" // 100 from first + 50 from second + "150" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" ); }) }); diff --git a/packages/subgraph/tests/mockedFunctions.ts b/packages/subgraph/tests/mockedFunctions.ts index 88629bdc57..7ef88761b5 100644 --- a/packages/subgraph/tests/mockedFunctions.ts +++ b/packages/subgraph/tests/mockedFunctions.ts @@ -379,3 +379,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 + ); +} From a0623c7d73d7ba6be3ccb93e6c65a21425f989f4 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Wed, 13 Mar 2024 13:03:42 +0200 Subject: [PATCH 15/17] fix test --- ...-07-pool-member-total-amount-received.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 index 4c9160e236..4d882de2a4 100644 --- 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 @@ -54,8 +54,8 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", poolAddress.toHexString(), poolAdminAndDistributorAddress.toHexString(), echo, - BigInt.fromI32(100), // requested amount - BigInt.fromI32(100), // actual amount + BigInt.fromI32(1000), // requested amount + BigInt.fromI32(1000), // actual amount Bytes.fromHexString("0x") ); firstDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; @@ -68,7 +68,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "Pool", poolAddress.toHexString(), "totalAmountDistributedUntilUpdatedAt", - "100" + "1000" ); assert.fieldEquals( "Pool", @@ -118,8 +118,8 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", poolAddress.toHexString(), poolAdminAndDistributorAddress.toHexString(), echo, - BigInt.fromI32(100), // requested amount - BigInt.fromI32(100), // actual amount + BigInt.fromI32(1000), // requested amount + BigInt.fromI32(1000), // actual amount Bytes.fromHexString("0x") ); secondDistributionEvent.block.timestamp = poolCreatedEvent.block.timestamp; @@ -132,7 +132,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "Pool", poolAddress.toHexString(), "totalAmountDistributedUntilUpdatedAt", - "200" + "2000" ); assert.fieldEquals( "Pool", @@ -173,7 +173,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "PoolMember", bobId, "totalAmountReceivedUntilUpdatedAt", - "50" + "500" ); assert.fieldEquals( "PoolMember", @@ -200,7 +200,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "PoolMember", aliceId, "totalAmountReceivedUntilUpdatedAt", - "150" + "1500" ); assert.fieldEquals( "PoolMember", From 5d1813d775319279abcd14c8e5f9d2a6d38d2694 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Wed, 13 Mar 2024 12:57:57 +0000 Subject: [PATCH 16/17] add elaborate gda test for flow distribution --- ...-pool-member-total-amount-received.test.ts | 270 ++++++++++++++++-- 1 file changed, 251 insertions(+), 19 deletions(-) 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 index 4d882de2a4..2d59c47411 100644 --- 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 @@ -2,29 +2,25 @@ import { assert, 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 { handleInstantDistributionUpdated } from "../../src/mappings/gdav1"; -import { createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent, createPoolAndReturnPoolCreatedEvent } from "../gdav1/gdav1.helper"; +import { handleFlowDistributionUpdated, handleInstantDistributionUpdated } from "../../src/mappings/gdav1"; +import { createFlowDistributionUpdatedEvent, createInstantDistributionUpdatedEvent, createMemberUnitsUpdatedEvent, createPoolAndReturnPoolCreatedEvent } from "../gdav1/gdav1.helper"; import { mockedAppManifestAndRealtimeBalanceOf } from "../mockedFunctions"; import { handleMemberUnitsUpdated } from "../../src/mappings/superfluidPool"; -/** - * Problem description - 1. Create pool - 2. Add member A and update A units to 100 - 3. Distribute 100 tokens - 4. Add member B and update B units to 100 - 4. Distribute 100 tokens - - Expected result: - member A 150 tokens - member B 50 tokens - - Actual result: - member A 100 tokens - member B 50 tokens - */ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", () => { - test("create elaborate scenario with 2 distributions and 2 pool members", () => { + /** + * 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); @@ -209,5 +205,241 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "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()); + + // ## 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 + 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); + + // 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); + + 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); + + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalAmountDistributedUntilUpdatedAt", + "2000" // Only for first flow rate + ); + assert.fieldEquals( + "Pool", + poolAddress.toHexString(), + "totalUnits", + "200" + ); + assert.fieldEquals( + "PoolMember", + bobId, + "totalAmountReceivedUntilUpdatedAt", + "500" // Bob just joined, shouldn't have any received + ); + assert.fieldEquals( + "PoolMember", + bobId, + "units", + "100" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "1500" + ); + // --- + + // 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(createBobEvent); + + 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); + + assert.fieldEquals( + "PoolMember", + aliceId, + "totalAmountReceivedUntilUpdatedAt", + "2500" + ); + assert.fieldEquals( + "PoolMember", + aliceId, + "units", + "100" + ); + }) }); \ No newline at end of file From 6b9675c8b47d1b7bfaddd69f7fd9d920720e7e93 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Thu, 14 Mar 2024 16:42:35 +0200 Subject: [PATCH 17/17] fix tests --- packages/subgraph/src/mappingHelpers.ts | 10 +--- packages/subgraph/src/mappings/gdav1.ts | 2 +- .../subgraph/src/mappings/superfluidPool.ts | 4 +- ...-pool-member-total-amount-received.test.ts | 48 ++++++++++++++++--- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/subgraph/src/mappingHelpers.ts b/packages/subgraph/src/mappingHelpers.ts index 27f629cca7..2f206ffed6 100644 --- a/packages/subgraph/src/mappingHelpers.ts +++ b/packages/subgraph/src/mappingHelpers.ts @@ -36,7 +36,6 @@ import { getPoolDistributorID, getActiveStreamsDelta, getClosedStreamsDelta, - MAX_UINT256, } from "./utils"; import { SuperToken as SuperTokenTemplate } from "../generated/templates"; import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; @@ -46,7 +45,6 @@ import { getResolverAddress, } from "./addresses"; import { FlowUpdated } from "../generated/ConstantFlowAgreementV1/IConstantFlowAgreementV1"; -import { log } from "matchstick-as"; /************************************************************************** * HOL initializer functions @@ -525,9 +523,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 @@ -1507,9 +1502,7 @@ export function monetaryUnitPoolMemberRTB(pool: Pool, poolMember: PoolMember, cu currentTimestamp, poolMember.updatedAtTimestamp ); - log.debug("poolPerUnitRTB {}", [poolPerUnitRTB.toString()]); - log.debug("poolMemberPerUnitRTB {}", [poolMemberPerUnitRTB.toString()]); - log.debug("poolMember.units {}", [poolMember.units.toString()]); + return poolMember.totalAmountReceivedUntilUpdatedAt.plus( poolPerUnitRTB.minus(poolMemberPerUnitRTB).times(poolMember.units) ); @@ -1558,7 +1551,6 @@ export function syncPoolMemberParticle(pool: Pool, poolMember: PoolMember): Pool } export function settlePDPoolMemberMU(pool: Pool, poolMember: PoolMember, block: ethereum.Block): void { - log.debug("pool.perUnitSettledValue {}", [pool.perUnitSettledValue.toString()]); pool = settlePoolParticle(pool, block); poolMember.totalAmountReceivedUntilUpdatedAt = monetaryUnitPoolMemberRTB(pool, poolMember, block.timestamp); poolMember = syncPoolMemberParticle(pool, poolMember); diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index 9c2cd3540c..19762416f7 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -101,8 +101,8 @@ 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 - settlePDPoolMemberMU(pool, poolMember, event.block); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); if (poolMember.units.gt(BIG_INT_ZERO)) { if (memberConnectedStatusUpdated) { // disconnected -> connected case diff --git a/packages/subgraph/src/mappings/superfluidPool.ts b/packages/subgraph/src/mappings/superfluidPool.ts index 2ba1c89077..12afde5e09 100644 --- a/packages/subgraph/src/mappings/superfluidPool.ts +++ b/packages/subgraph/src/mappings/superfluidPool.ts @@ -28,8 +28,8 @@ export function handleDistributionClaimed(event: DistributionClaimed): void { poolMember.totalAmountClaimed = event.params.totalClaimed; // settle pool and pool member - settlePDPoolMemberMU(pool, poolMember, event.block); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); // Update PoolMember poolMember.totalAmountClaimed = event.params.totalClaimed; @@ -58,8 +58,8 @@ export function handleMemberUnitsUpdated(event: MemberUnitsUpdated): void { const unitsDelta = event.params.newUnits.minus(previousUnits); const newTotalUnits = pool.totalUnits.plus(unitsDelta); - settlePDPoolMemberMU(pool, poolMember, event.block); pool = updatePoolTotalAmountFlowedAndDistributed(event, pool); + settlePDPoolMemberMU(pool, poolMember, event.block); // @note TODO update the pool.perUnitFlowRate // @note TODO update the poolMember.perUnitFlowRate 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 index 2d59c47411..fdd04955af 100644 --- 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 @@ -1,13 +1,18 @@ -import { assert, describe, test } from "matchstick-as"; +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 } from "../../src/mappings/gdav1"; +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 @@ -204,7 +209,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "units", "100" ); - }) + }); /** * Problem description 1. Create pool @@ -233,6 +238,14 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", 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); @@ -249,6 +262,10 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", handleMemberUnitsUpdated(aliceCreatedEvent); // # First flow rate + if (pool) { + pool.updatedAtTimestamp = aliceCreatedEvent.block.timestamp; + } + const firstFlowRateEvent = createFlowDistributionUpdatedEvent( superTokenAddress, poolAddress.toHexString(), @@ -267,6 +284,11 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", 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", @@ -316,6 +338,10 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), createBobEvent.block.timestamp); handleMemberUnitsUpdated(createBobEvent); + if (pool) { + pool.updatedAtTimestamp = createBobEvent.block.timestamp; + } + assert.fieldEquals( "Pool", poolAddress.toHexString(), @@ -348,6 +374,10 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, poolAdminAndDistributorAddress.toHexString(), secondFlowRateEvent.block.timestamp); handleFlowDistributionUpdated(secondFlowRateEvent); + if (pool) { + pool.updatedAtTimestamp = secondFlowRateEvent.block.timestamp; + } + assert.fieldEquals( "Pool", poolAddress.toHexString(), @@ -364,7 +394,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "PoolMember", bobId, "totalAmountReceivedUntilUpdatedAt", - "500" // Bob just joined, shouldn't have any received + "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", @@ -376,7 +406,7 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", "PoolMember", aliceId, "totalAmountReceivedUntilUpdatedAt", - "1500" + "0" // it's 1500 if we query on-chain, but 0 here because update member units hasn't been called again since ); // --- @@ -393,8 +423,11 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", updateBobEvent.block.timestamp = BigInt.fromI32(4); // 4 - 1 = 3 seconds of flow mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, bobAddress.toHexString(), updateBobEvent.block.timestamp); - handleMemberUnitsUpdated(createBobEvent); + handleMemberUnitsUpdated(updateBobEvent); + if (pool) { + pool.updatedAtTimestamp = updateBobEvent.block.timestamp; + } assert.fieldEquals( "Pool", poolAddress.toHexString(), @@ -428,6 +461,9 @@ describe("PoolMember ending up with wrong `totalAmountReceivedUntilUpdatedAt`", mockedAppManifestAndRealtimeBalanceOf(superTokenAddress, aliceAddress.toHexString(), updateAliceEvent.block.timestamp); handleMemberUnitsUpdated(updateAliceEvent); + if (pool) { + pool.updatedAtTimestamp = updateAliceEvent.block.timestamp; + } assert.fieldEquals( "PoolMember", aliceId,