diff --git a/packages/subgraph/src/mappings/gdav1.ts b/packages/subgraph/src/mappings/gdav1.ts index a71ef220c8..f870d986dd 100644 --- a/packages/subgraph/src/mappings/gdav1.ts +++ b/packages/subgraph/src/mappings/gdav1.ts @@ -197,11 +197,28 @@ export function handleBufferAdjusted(event: BufferAdjusted): void { event.params.token, event.block ); + tokenStatistic.totalDeposit = tokenStatistic.totalDeposit.plus( + event.params.bufferDelta + ); tokenStatistic.totalGDADeposit = tokenStatistic.totalGDADeposit.plus( event.params.bufferDelta ); tokenStatistic.save(); + // Update AccountTokenSnapshot for the pool distributor (fixes #2155) + const accountTokenSnapshot = getOrInitAccountTokenSnapshot( + event.params.from, + event.params.token, + event.block + ); + accountTokenSnapshot.totalGDADeposit = accountTokenSnapshot.totalGDADeposit.plus( + event.params.bufferDelta + ); + accountTokenSnapshot.totalDeposit = accountTokenSnapshot.totalDeposit.plus( + event.params.bufferDelta + ); + accountTokenSnapshot.save(); + // Create Event Entity _createBufferAdjustedEntity(event, poolDistributor.id); } diff --git a/packages/subgraph/tests/bugs/2026-03-19-ats-totalgdadeposit-on-buffer-adjusted.test.ts b/packages/subgraph/tests/bugs/2026-03-19-ats-totalgdadeposit-on-buffer-adjusted.test.ts new file mode 100644 index 0000000000..eb9ba80623 --- /dev/null +++ b/packages/subgraph/tests/bugs/2026-03-19-ats-totalgdadeposit-on-buffer-adjusted.test.ts @@ -0,0 +1,113 @@ +/** + * Issue: https://github.com/superfluid-org/protocol-monorepo/issues/2155 + * + * AccountTokenSnapshot.totalGDADeposit (and totalDeposit) can be 0 in the subgraph + * when the account actually has nonzero GDA buffer on-chain. + * + * Root cause: handleBufferAdjusted updates TokenStatistic.totalGDADeposit but does + * NOT update AccountTokenSnapshot.totalGDADeposit for the pool distributor (from). + */ +import { BigInt } from "@graphprotocol/graph-ts"; +import { assert, beforeEach, clearStore, describe, test } from "matchstick-as/assembly/index"; +import { handleBufferAdjusted } from "../../src/mappings/gdav1"; +import { createBufferAdjustedEvent, createPoolAndReturnPoolCreatedEvent } from "../gdav1/gdav1.helper"; +import { alice, maticXAddress, superfluidPool } from "../constants"; +import { BIG_INT_ZERO } from "../../src/utils"; + +describe("Issue 2155: AccountTokenSnapshot totalGDADeposit on BufferAdjusted", () => { + beforeEach(() => { + clearStore(); + }); + + test("handleBufferAdjusted() should update account and token deposits for the pool distributor", () => { + // Create pool with alice as admin so AccountTokenSnapshot exists for alice + createPoolAndReturnPoolCreatedEvent(alice, maticXAddress, superfluidPool, BIG_INT_ZERO); + + const bufferDelta = BigInt.fromI32(526847904); // e.g. 526e9 wei + const newBufferAmount = bufferDelta; + const totalBufferAmount = bufferDelta; + const poolDistributor = alice; + + const bufferAdjustedEvent = createBufferAdjustedEvent( + maticXAddress, + superfluidPool, + poolDistributor, + bufferDelta, + newBufferAmount, + totalBufferAmount + ); + + handleBufferAdjusted(bufferAdjustedEvent); + + const accountTokenSnapshotId = poolDistributor + "-" + maticXAddress; + assert.fieldEquals( + "TokenStatistic", + maticXAddress, + "totalGDADeposit", + bufferDelta.toString() + ); + assert.fieldEquals( + "TokenStatistic", + maticXAddress, + "totalDeposit", + bufferDelta.toString() + ); + assert.fieldEquals( + "AccountTokenSnapshot", + accountTokenSnapshotId, + "totalGDADeposit", + bufferDelta.toString() + ); + assert.fieldEquals( + "AccountTokenSnapshot", + accountTokenSnapshotId, + "totalDeposit", + bufferDelta.toString() + ); + }); + + test("handleBufferAdjusted() should decrement account and token deposits when buffer shrinks", () => { + createPoolAndReturnPoolCreatedEvent(alice, maticXAddress, superfluidPool, BIG_INT_ZERO); + + const poolDistributor = alice; + const depositAmount = BigInt.fromI32(526847904); + const withdrawAmount = BigInt.fromI32(-526847904); + + handleBufferAdjusted( + createBufferAdjustedEvent( + maticXAddress, + superfluidPool, + poolDistributor, + depositAmount, + depositAmount, + depositAmount + ) + ); + handleBufferAdjusted( + createBufferAdjustedEvent( + maticXAddress, + superfluidPool, + poolDistributor, + withdrawAmount, + BIG_INT_ZERO, + BIG_INT_ZERO + ) + ); + + const accountTokenSnapshotId = poolDistributor + "-" + maticXAddress; + assert.fieldEquals("TokenStatistic", maticXAddress, "totalGDADeposit", "0"); + assert.fieldEquals("TokenStatistic", maticXAddress, "totalDeposit", "0"); + assert.fieldEquals( + "AccountTokenSnapshot", + accountTokenSnapshotId, + "totalGDADeposit", + "0" + ); + assert.fieldEquals( + "AccountTokenSnapshot", + accountTokenSnapshotId, + "totalDeposit", + "0" + ); + }); +}); diff --git a/packages/subgraph/tests/converters.ts b/packages/subgraph/tests/converters.ts index 56cf6bdaaf..2f64a6e086 100644 --- a/packages/subgraph/tests/converters.ts +++ b/packages/subgraph/tests/converters.ts @@ -148,6 +148,22 @@ export function getBigIntEventParam( ); } +/** + * Takes a signed BigInt value and returns a BigInt ethereum.EventParam object + * @param name the name of the parameter (must match actual value from contracts) + * @param value BigInt parameter value + * @returns ethereum.EventParam + */ +export function getSignedBigIntEventParam( + name: string, + value: BigInt +): ethereum.EventParam { + return new ethereum.EventParam( + name, + ethereum.Value.fromSignedBigInt(value) + ); +} + /** * Takes an i32 value and returns an i32 ethereum.EventParam object * @param name the name of the parameter (must match actual value from contracts) diff --git a/packages/subgraph/tests/gdav1/gdav1.helper.ts b/packages/subgraph/tests/gdav1/gdav1.helper.ts index d170b5b2fa..f9154ad7d8 100644 --- a/packages/subgraph/tests/gdav1/gdav1.helper.ts +++ b/packages/subgraph/tests/gdav1/gdav1.helper.ts @@ -10,7 +10,13 @@ import { DistributionClaimed, MemberUnitsUpdated, } from "../../generated/GeneralDistributionAgreementV1/ISuperfluidPool"; -import { getAddressEventParam, getBigIntEventParam, getBooleanEventParam, getBytesEventParam } from "../converters"; +import { + getAddressEventParam, + getBigIntEventParam, + getBooleanEventParam, + getBytesEventParam, + getSignedBigIntEventParam, +} from "../converters"; import { BigInt, Bytes } from "@graphprotocol/graph-ts"; import { handlePoolConnectionUpdated, handlePoolCreated } from "../../src/mappings/gdav1"; import { BIG_INT_ZERO } from "../../src/utils"; @@ -131,7 +137,7 @@ export function createBufferAdjustedEvent( newBufferAdjustedEvent.parameters.push(getAddressEventParam("token", token)); newBufferAdjustedEvent.parameters.push(getAddressEventParam("pool", pool)); newBufferAdjustedEvent.parameters.push(getAddressEventParam("poolDistributor", poolDistributor)); - newBufferAdjustedEvent.parameters.push(getBigIntEventParam("bufferDelta", bufferDelta)); + newBufferAdjustedEvent.parameters.push(getSignedBigIntEventParam("bufferDelta", bufferDelta)); newBufferAdjustedEvent.parameters.push(getBigIntEventParam("newBufferAmount", newBufferAmount)); newBufferAdjustedEvent.parameters.push(getBigIntEventParam("totalBufferAmount", totalBufferAmount));