diff --git a/projects/subgraph-basin/manifests/arbitrum.yaml b/projects/subgraph-basin/manifests/arbitrum.yaml index 2af7018a8..1b719d439 100644 --- a/projects/subgraph-basin/manifests/arbitrum.yaml +++ b/projects/subgraph-basin/manifests/arbitrum.yaml @@ -52,25 +52,27 @@ dataSources: handler: handleBoreWell file: ../src/handlers/AquiferTemplateHandler.ts - kind: ethereum/contract - name: Beanstalk + name: HourlySnapshots network: arbitrum-one source: - address: "0xD1A0060ba708BC4BCD3DA6C37EFa8deDF015FB70" - abi: Beanstalk - startBlock: 261772156 # Reseed + address: "0xBA51AAAa8C2f911AE672e783707Ceb2dA6E97521" + abi: Aquifer + startBlock: 261000001 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Beanstalk + - Well abis: - - name: Beanstalk - file: ../../subgraph-core/abis/Beanstalk/Beanstalk-BIP50.json - eventHandlers: - - event: Sunrise(indexed uint256) - handler: handleSunrise - file: ../src/handlers/BeanstalkHandler.ts + - name: Aquifer + file: ../../subgraph-core/abis/Aquifer.json + blockHandlers: + - handler: handleBlock + filter: + kind: polling + every: 1200 # 5 minutes on arbitrum + file: ../src/handlers/HourlySnapshotHandler.ts templates: - kind: ethereum/contract name: Well diff --git a/projects/subgraph-basin/manifests/ethereum.yaml b/projects/subgraph-basin/manifests/ethereum.yaml index e1b6ef28a..373e7abf6 100644 --- a/projects/subgraph-basin/manifests/ethereum.yaml +++ b/projects/subgraph-basin/manifests/ethereum.yaml @@ -50,25 +50,27 @@ dataSources: handler: handleBoreWell file: ../src/handlers/AquiferTemplateHandler.ts - kind: ethereum/contract - name: Beanstalk + name: HourlySnapshots network: mainnet source: - address: "0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5" - abi: Beanstalk - startBlock: 17977922 + address: "0xBA51AAAA95aeEFc1292515b36D86C51dC7877773" + abi: Aquifer + startBlock: 17977923 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Beanstalk + - Well abis: - - name: Beanstalk - file: ../../subgraph-core/abis/Beanstalk/Beanstalk-BIP50.json - eventHandlers: - - event: Sunrise(indexed uint256) - handler: handleSunrise - file: ../src/handlers/BeanstalkHandler.ts + - name: Aquifer + file: ../../subgraph-core/abis/Aquifer.json + blockHandlers: + - handler: handleBlock + filter: + kind: polling + every: 25 # 5 minutes on ethereum + file: ../src/handlers/HourlySnapshotHandler.ts templates: - kind: ethereum/contract name: Well diff --git a/projects/subgraph-basin/src/entities/Well.ts b/projects/subgraph-basin/src/entities/Well.ts index f5fd86ca2..41174efde 100644 --- a/projects/subgraph-basin/src/entities/Well.ts +++ b/projects/subgraph-basin/src/entities/Well.ts @@ -1,4 +1,4 @@ -import { Address, BigInt, Bytes, ethereum } from "@graphprotocol/graph-ts"; +import { Address, BigDecimal, BigInt, Bytes, ethereum } from "@graphprotocol/graph-ts"; import { Well, WellDailySnapshot, @@ -247,7 +247,7 @@ export function takeWellDailySnapshot(wellAddress: Address, dayID: i32, block: e let newSnapshot = loadOrCreateWellDailySnapshot(wellAddress, well.lastSnapshotDayID, block); newSnapshot.deltalpTokenSupply = newSnapshot.lpTokenSupply.minus(priorSnapshot.lpTokenSupply); - newSnapshot.deltaLiquidityUSD = newSnapshot.totalLiquidityUSD.minus(priorSnapshot.totalLiquidityUSD); + newSnapshot.deltaLiquidityUSD = newSnapshot.totalLiquidityUSD.minus(priorSnapshot.totalLiquidityUSD).truncate(2); newSnapshot.deltaTradeVolumeReserves = subBigIntArray( newSnapshot.cumulativeTradeVolumeReserves, @@ -256,8 +256,8 @@ export function takeWellDailySnapshot(wellAddress: Address, dayID: i32, block: e newSnapshot.deltaTradeVolumeReservesUSD = subBigDecimalArray( newSnapshot.cumulativeTradeVolumeReservesUSD, priorSnapshot.cumulativeTradeVolumeReservesUSD - ); - newSnapshot.deltaTradeVolumeUSD = newSnapshot.cumulativeTradeVolumeUSD.minus(priorSnapshot.cumulativeTradeVolumeUSD); + ).map((bd) => bd.truncate(2)); + newSnapshot.deltaTradeVolumeUSD = newSnapshot.cumulativeTradeVolumeUSD.minus(priorSnapshot.cumulativeTradeVolumeUSD).truncate(2); newSnapshot.deltaBiTradeVolumeReserves = subBigIntArray( newSnapshot.cumulativeBiTradeVolumeReserves, priorSnapshot.cumulativeBiTradeVolumeReserves @@ -269,8 +269,8 @@ export function takeWellDailySnapshot(wellAddress: Address, dayID: i32, block: e newSnapshot.deltaTransferVolumeReservesUSD = subBigDecimalArray( newSnapshot.cumulativeTransferVolumeReservesUSD, priorSnapshot.cumulativeTransferVolumeReservesUSD - ); - newSnapshot.deltaTransferVolumeUSD = newSnapshot.cumulativeTransferVolumeUSD.minus(priorSnapshot.cumulativeTransferVolumeUSD); + ).map((bd) => bd.truncate(2)); + newSnapshot.deltaTransferVolumeUSD = newSnapshot.cumulativeTransferVolumeUSD.minus(priorSnapshot.cumulativeTransferVolumeUSD).truncate(2); newSnapshot.deltaDepositCount = newSnapshot.cumulativeDepositCount - priorSnapshot.cumulativeDepositCount; newSnapshot.deltaWithdrawCount = newSnapshot.cumulativeWithdrawCount - priorSnapshot.cumulativeWithdrawCount; @@ -285,13 +285,12 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: let priorHourID = well.lastSnapshotHourID; well.lastSnapshotHourID = hourID; - well.save(); let priorSnapshot = loadOrCreateWellHourlySnapshot(wellAddress, priorHourID, block); let newSnapshot = loadOrCreateWellHourlySnapshot(wellAddress, hourID, block); newSnapshot.deltalpTokenSupply = newSnapshot.lpTokenSupply.minus(priorSnapshot.lpTokenSupply); - newSnapshot.deltaLiquidityUSD = newSnapshot.totalLiquidityUSD.minus(priorSnapshot.totalLiquidityUSD); + newSnapshot.deltaLiquidityUSD = newSnapshot.totalLiquidityUSD.minus(priorSnapshot.totalLiquidityUSD).truncate(2); newSnapshot.deltaTradeVolumeReserves = subBigIntArray( newSnapshot.cumulativeTradeVolumeReserves, @@ -300,8 +299,8 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: newSnapshot.deltaTradeVolumeReservesUSD = subBigDecimalArray( newSnapshot.cumulativeTradeVolumeReservesUSD, priorSnapshot.cumulativeTradeVolumeReservesUSD - ); - newSnapshot.deltaTradeVolumeUSD = newSnapshot.cumulativeTradeVolumeUSD.minus(priorSnapshot.cumulativeTradeVolumeUSD); + ).map((bd) => bd.truncate(2)); + newSnapshot.deltaTradeVolumeUSD = newSnapshot.cumulativeTradeVolumeUSD.minus(priorSnapshot.cumulativeTradeVolumeUSD).truncate(2); newSnapshot.deltaBiTradeVolumeReserves = subBigIntArray( newSnapshot.cumulativeBiTradeVolumeReserves, priorSnapshot.cumulativeBiTradeVolumeReserves @@ -313,8 +312,8 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: newSnapshot.deltaTransferVolumeReservesUSD = subBigDecimalArray( newSnapshot.cumulativeTransferVolumeReservesUSD, priorSnapshot.cumulativeTransferVolumeReservesUSD - ); - newSnapshot.deltaTransferVolumeUSD = newSnapshot.cumulativeTransferVolumeUSD.minus(priorSnapshot.cumulativeTransferVolumeUSD); + ).map((bd) => bd.truncate(2)); + newSnapshot.deltaTransferVolumeUSD = newSnapshot.cumulativeTransferVolumeUSD.minus(priorSnapshot.cumulativeTransferVolumeUSD).truncate(2); newSnapshot.deltaDepositCount = newSnapshot.cumulativeDepositCount - priorSnapshot.cumulativeDepositCount; newSnapshot.deltaWithdrawCount = newSnapshot.cumulativeWithdrawCount - priorSnapshot.cumulativeWithdrawCount; @@ -332,8 +331,8 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: well.rollingDailyTradeVolumeReservesUSD = subBigDecimalArray( well.rollingDailyTradeVolumeReservesUSD, oldest24h.deltaTradeVolumeReservesUSD - ); - well.rollingDailyTradeVolumeUSD = well.rollingDailyTradeVolumeUSD.minus(oldest24h.deltaTradeVolumeUSD); + ).map((bd) => bd.truncate(2)); + well.rollingDailyTradeVolumeUSD = well.rollingDailyTradeVolumeUSD.minus(oldest24h.deltaTradeVolumeUSD).truncate(2); well.rollingDailyBiTradeVolumeReserves = subBigIntArray(well.rollingDailyBiTradeVolumeReserves, oldest24h.deltaBiTradeVolumeReserves); well.rollingDailyTransferVolumeReserves = subBigIntArray( well.rollingDailyTransferVolumeReserves, @@ -342,15 +341,15 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: well.rollingDailyTransferVolumeReservesUSD = subBigDecimalArray( well.rollingDailyTransferVolumeReservesUSD, oldest24h.deltaTransferVolumeReservesUSD - ); - well.rollingDailyTransferVolumeUSD = well.rollingDailyTransferVolumeUSD.minus(oldest24h.deltaTransferVolumeUSD); + ).map((bd) => bd.truncate(2)); + well.rollingDailyTransferVolumeUSD = well.rollingDailyTransferVolumeUSD.minus(oldest24h.deltaTransferVolumeUSD).truncate(2); if (oldest7d != null) { well.rollingWeeklyTradeVolumeReserves = subBigIntArray(well.rollingWeeklyTradeVolumeReserves, oldest7d.deltaTradeVolumeReserves); well.rollingWeeklyTradeVolumeReservesUSD = subBigDecimalArray( well.rollingWeeklyTradeVolumeReservesUSD, oldest7d.deltaTradeVolumeReservesUSD - ); - well.rollingWeeklyTradeVolumeUSD = well.rollingWeeklyTradeVolumeUSD.minus(oldest7d.deltaTradeVolumeUSD); + ).map((bd) => bd.truncate(2)); + well.rollingWeeklyTradeVolumeUSD = well.rollingWeeklyTradeVolumeUSD.minus(oldest7d.deltaTradeVolumeUSD).truncate(2); well.rollingWeeklyBiTradeVolumeReserves = subBigIntArray( well.rollingWeeklyBiTradeVolumeReserves, oldest7d.deltaBiTradeVolumeReserves @@ -362,9 +361,11 @@ export function takeWellHourlySnapshot(wellAddress: Address, hourID: i32, block: well.rollingWeeklyTransferVolumeReservesUSD = subBigDecimalArray( well.rollingWeeklyTransferVolumeReservesUSD, oldest7d.deltaTransferVolumeReservesUSD - ); - well.rollingWeeklyTransferVolumeUSD = well.rollingWeeklyTransferVolumeUSD.minus(oldest7d.deltaTransferVolumeUSD); + ).map((bd) => bd.truncate(2)); + well.rollingWeeklyTransferVolumeUSD = well.rollingWeeklyTransferVolumeUSD.minus(oldest7d.deltaTransferVolumeUSD).truncate(2); } } + well.lastUpdateTimestamp = block.timestamp; + well.lastUpdateBlockNumber = block.number; well.save(); } diff --git a/projects/subgraph-basin/src/handlers/BeanstalkHandler.ts b/projects/subgraph-basin/src/handlers/HourlySnapshotHandler.ts similarity index 54% rename from projects/subgraph-basin/src/handlers/BeanstalkHandler.ts rename to projects/subgraph-basin/src/handlers/HourlySnapshotHandler.ts index f9779861d..edf5f3253 100644 --- a/projects/subgraph-basin/src/handlers/BeanstalkHandler.ts +++ b/projects/subgraph-basin/src/handlers/HourlySnapshotHandler.ts @@ -1,18 +1,17 @@ -import { v } from "../utils/constants/Version"; -import { Sunrise } from "../../generated/Basin-ABIs/Beanstalk"; +import { ethereum } from "@graphprotocol/graph-ts"; import { checkForSnapshot } from "../utils/Well"; -import { getAquifer } from "../../../subgraph-core/constants/RuntimeConstants"; import { toAddress } from "../../../subgraph-core/utils/Bytes"; import { loadOrCreateAquifer } from "../entities/WellComponents"; +import { v } from "../utils/constants/Version"; +import { getAquifer } from "../../../subgraph-core/constants/RuntimeConstants"; -export function handleSunrise(event: Sunrise): void { - // Right now this is a manual list of aquifers that are checked for deployments and wells updated - // Keeping this manual is reasonable as each aquifer has to be defined as a datasource in subgraph.yaml - +// Used to take hourly snapshots in the absense of pool trading activity. +// This handler should be configured for infrequent polling +export function handleBlock(block: ethereum.Block): void { const aquifer = loadOrCreateAquifer(getAquifer(v())); const wells = aquifer.wells.load(); for (let i = 0; i < wells.length; i++) { - checkForSnapshot(toAddress(wells[i].id), event.block); + checkForSnapshot(toAddress(wells[i].id), block); } } diff --git a/projects/subgraph-basin/src/utils/Volume.ts b/projects/subgraph-basin/src/utils/Volume.ts index e3f6e38ea..1a294b507 100644 --- a/projects/subgraph-basin/src/utils/Volume.ts +++ b/projects/subgraph-basin/src/utils/Volume.ts @@ -128,16 +128,16 @@ function updateVolumeStats(well: Well, deltaTradeVolumeReserves: BigInt[], delta rollingWeeklyTransferVolumeReserves[i] = rollingWeeklyTransferVolumeReserves[i].plus(deltaTransferVolumeReserves[i].abs()); let usdTransferAmount = toDecimal(deltaTransferVolumeReserves[i].abs(), tokenInfo.decimals).times(tokenInfo.lastPriceUSD); - tradeVolumeReservesUSD[i] = tradeVolumeReservesUSD[i].plus(usdTradeAmount); - rollingDailyTradeVolumeReservesUSD[i] = rollingDailyTradeVolumeReservesUSD[i].plus(usdTradeAmount); - rollingWeeklyTradeVolumeReservesUSD[i] = rollingWeeklyTradeVolumeReservesUSD[i].plus(usdTradeAmount); + tradeVolumeReservesUSD[i] = tradeVolumeReservesUSD[i].plus(usdTradeAmount).truncate(2); + rollingDailyTradeVolumeReservesUSD[i] = rollingDailyTradeVolumeReservesUSD[i].plus(usdTradeAmount).truncate(2); + rollingWeeklyTradeVolumeReservesUSD[i] = rollingWeeklyTradeVolumeReservesUSD[i].plus(usdTradeAmount).truncate(2); - transferVolumeReservesUSD[i] = transferVolumeReservesUSD[i].plus(usdTransferAmount); - rollingDailyTransferVolumeReservesUSD[i] = rollingDailyTransferVolumeReservesUSD[i].plus(usdTransferAmount); - rollingWeeklyTransferVolumeReservesUSD[i] = rollingWeeklyTransferVolumeReservesUSD[i].plus(usdTransferAmount); + transferVolumeReservesUSD[i] = transferVolumeReservesUSD[i].plus(usdTransferAmount).truncate(2); + rollingDailyTransferVolumeReservesUSD[i] = rollingDailyTransferVolumeReservesUSD[i].plus(usdTransferAmount).truncate(2); + rollingWeeklyTransferVolumeReservesUSD[i] = rollingWeeklyTransferVolumeReservesUSD[i].plus(usdTransferAmount).truncate(2); - totalTradeUSD = totalTradeUSD.plus(usdTradeAmount); - totalTransferUSD = totalTransferUSD.plus(usdTransferAmount); + totalTradeUSD = totalTradeUSD.plus(usdTradeAmount).truncate(2); + totalTransferUSD = totalTransferUSD.plus(usdTransferAmount).truncate(2); } well.cumulativeTradeVolumeReserves = tradeVolumeReserves; @@ -154,19 +154,19 @@ function updateVolumeStats(well: Well, deltaTradeVolumeReserves: BigInt[], delta // but this is preferable to having the most recent values being delayed. well.rollingDailyTradeVolumeReserves = rollingDailyTradeVolumeReserves; well.rollingDailyTradeVolumeReservesUSD = rollingDailyTradeVolumeReservesUSD; - well.rollingDailyTradeVolumeUSD = well.rollingDailyTradeVolumeUSD.plus(totalTradeUSD); + well.rollingDailyTradeVolumeUSD = well.rollingDailyTradeVolumeUSD.plus(totalTradeUSD).truncate(2); well.rollingDailyBiTradeVolumeReserves = rollingDailyBiTradeVolumeReserves; well.rollingDailyTransferVolumeReserves = rollingDailyTransferVolumeReserves; well.rollingDailyTransferVolumeReservesUSD = rollingDailyTransferVolumeReservesUSD; - well.rollingDailyTransferVolumeUSD = well.rollingDailyTransferVolumeUSD.plus(totalTransferUSD); + well.rollingDailyTransferVolumeUSD = well.rollingDailyTransferVolumeUSD.plus(totalTransferUSD).truncate(2); well.rollingWeeklyTradeVolumeReserves = rollingWeeklyTradeVolumeReserves; well.rollingWeeklyTradeVolumeReservesUSD = rollingWeeklyTradeVolumeReservesUSD; - well.rollingWeeklyTradeVolumeUSD = well.rollingWeeklyTradeVolumeUSD.plus(totalTradeUSD); + well.rollingWeeklyTradeVolumeUSD = well.rollingWeeklyTradeVolumeUSD.plus(totalTradeUSD).truncate(2); well.rollingWeeklyBiTradeVolumeReserves = rollingWeeklyBiTradeVolumeReserves; well.rollingWeeklyTransferVolumeReserves = rollingWeeklyTransferVolumeReserves; well.rollingWeeklyTransferVolumeReservesUSD = rollingWeeklyTransferVolumeReservesUSD; - well.rollingWeeklyTransferVolumeUSD = well.rollingWeeklyTransferVolumeUSD.plus(totalTransferUSD); + well.rollingWeeklyTransferVolumeUSD = well.rollingWeeklyTransferVolumeUSD.plus(totalTransferUSD).truncate(2); } // Returns the provided token amounts in their appropriate position with respect to well reserve tokens diff --git a/projects/subgraph-basin/src/utils/Well.ts b/projects/subgraph-basin/src/utils/Well.ts index 16bd6c52f..df00286b8 100644 --- a/projects/subgraph-basin/src/utils/Well.ts +++ b/projects/subgraph-basin/src/utils/Well.ts @@ -56,8 +56,8 @@ export function updateWellTokenUSDPrices(wellAddress: Address, blockNumber: BigI } } - well.reservesUSD = getCalculatedReserveUSDValues(well.tokens, well.reserves); - well.totalLiquidityUSD = getBigDecimalArrayTotal(well.reservesUSD); + well.reservesUSD = getCalculatedReserveUSDValues(well.tokens, well.reserves).map((bd) => bd.truncate(2)); + well.totalLiquidityUSD = getBigDecimalArrayTotal(well.reservesUSD).truncate(2); well.save(); } diff --git a/projects/subgraph-basin/src/utils/constants/Version.ts b/projects/subgraph-basin/src/utils/constants/Version.ts index c6f06456e..fb09af133 100644 --- a/projects/subgraph-basin/src/utils/constants/Version.ts +++ b/projects/subgraph-basin/src/utils/constants/Version.ts @@ -7,7 +7,7 @@ import * as BeanstalkArb from "../../../../subgraph-core/constants/raw/Beanstalk export function handleInitVersion(block: ethereum.Block): void { const versionEntity = new Version("subgraph"); - versionEntity.versionNumber = "3.1.0"; + versionEntity.versionNumber = "3.1.1"; versionEntity.subgraphName = subgraphNameForBlockNumber(block.number); versionEntity.protocolAddress = protocolForBlockNumber(block.number); versionEntity.chain = chainForBlockNumber(block.number); diff --git a/projects/subgraph-basin/tests/Exchange.test.ts b/projects/subgraph-basin/tests/Exchange.test.ts index e964e1166..d5106dde6 100644 --- a/projects/subgraph-basin/tests/Exchange.test.ts +++ b/projects/subgraph-basin/tests/Exchange.test.ts @@ -19,6 +19,7 @@ import { dayFromTimestamp, hourFromTimestamp } from "../../subgraph-core/utils/D import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; import { BEAN_ERC20 } from "../../subgraph-core/constants/raw/BeanstalkEthConstants"; import { initL1Version } from "./entity-mocking/MockVersion"; +import { assertBDClose } from "../../subgraph-core/tests/Assert"; describe("Well Entity: Exchange Tests", () => { beforeEach(() => { @@ -66,15 +67,13 @@ describe("Well Entity: Exchange Tests", () => { let transferAmounts = updatedStore.cumulativeTransferVolumeReservesUSD; assert.stringEquals("0", tradeAmounts[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("1.5")).toString(), tradeAmounts[1].toString()); - assert.stringEquals(BEAN_USD_AMOUNT.times(BigDecimal.fromString("2.5")).toString(), transferAmounts[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("3.5")).toString(), transferAmounts[1].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("1.5")).toString(), updatedStore.cumulativeTradeVolumeUSD.toString()); - assert.stringEquals( - BEAN_USD_AMOUNT.times(BigDecimal.fromString("2.5")) - .plus(WETH_USD_AMOUNT.times(BigDecimal.fromString("3.5"))) - .toString(), - updatedStore.cumulativeTransferVolumeUSD.toString() + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("1.5")), tradeAmounts[1]); + assertBDClose(BEAN_USD_AMOUNT.times(BigDecimal.fromString("2.5")), transferAmounts[0]); + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("3.5")), transferAmounts[1]); + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("1.5")), updatedStore.cumulativeTradeVolumeUSD); + assertBDClose( + BEAN_USD_AMOUNT.times(BigDecimal.fromString("2.5")).plus(WETH_USD_AMOUNT.times(BigDecimal.fromString("3.5"))), + updatedStore.cumulativeTransferVolumeUSD ); }); test("Previous day snapshot entity created", () => { @@ -123,16 +122,14 @@ describe("Well Entity: Exchange Tests", () => { let tradeAmounts = updatedStore.cumulativeTradeVolumeReservesUSD; let transferAmounts = updatedStore.cumulativeTransferVolumeReservesUSD; - assert.stringEquals(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")).toString(), tradeAmounts[0].toString()); + assertBDClose(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")), tradeAmounts[0]); assert.stringEquals("0", tradeAmounts[1].toString()); - assert.stringEquals(BEAN_USD_AMOUNT.times(BigDecimal.fromString("3.5")).toString(), transferAmounts[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("2.5")).toString(), transferAmounts[1].toString()); - assert.stringEquals(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")).toString(), updatedStore.cumulativeTradeVolumeUSD.toString()); - assert.stringEquals( - BEAN_USD_AMOUNT.times(BigDecimal.fromString("3.5")) - .plus(WETH_USD_AMOUNT.times(BigDecimal.fromString("2.5"))) - .toString(), - updatedStore.cumulativeTransferVolumeUSD.toString() + assertBDClose(BEAN_USD_AMOUNT.times(BigDecimal.fromString("3.5")), transferAmounts[0]); + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("2.5")), transferAmounts[1]); + assertBDClose(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")), updatedStore.cumulativeTradeVolumeUSD); + assertBDClose( + BEAN_USD_AMOUNT.times(BigDecimal.fromString("3.5")).plus(WETH_USD_AMOUNT.times(BigDecimal.fromString("2.5"))), + updatedStore.cumulativeTransferVolumeUSD ); }); }); diff --git a/projects/subgraph-basin/tests/Liquidity.test.ts b/projects/subgraph-basin/tests/Liquidity.test.ts index eabab024a..2e123e456 100644 --- a/projects/subgraph-basin/tests/Liquidity.test.ts +++ b/projects/subgraph-basin/tests/Liquidity.test.ts @@ -25,6 +25,7 @@ import { toAddress } from "../../subgraph-core/utils/Bytes"; import { mockWellLpTokenUnderlying } from "../../subgraph-core/tests/event-mocking/Tokens"; import { deprecated_calcLiquidityVolume } from "../src/utils/legacy/CP2"; import { loadOrCreateWellFunction } from "../src/entities/WellComponents"; +import { assertBDClose } from "../../subgraph-core/tests/Assert"; const BI_2 = BigInt.fromU32(2); const BI_3 = BigInt.fromU32(3); @@ -59,9 +60,9 @@ describe("Well Entity: Liquidity Event Tests", () => { let updatedStore = loadWell(WELL); let endingBalances = updatedStore.reservesUSD; - assert.stringEquals(BEAN_USD_AMOUNT.toString(), endingBalances[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), endingBalances[1].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("2")).toString(), updatedStore.totalLiquidityUSD.toString()); + assertBDClose(BEAN_USD_AMOUNT, endingBalances[0]); + assertBDClose(WETH_USD_AMOUNT, endingBalances[1]); + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("2")), updatedStore.totalLiquidityUSD); }); test("Liquidity Token balance", () => { assert.fieldEquals(WELL_ENTITY_TYPE, WELL.toHexString(), "lpTokenSupply", WELL_LP_AMOUNT.toString()); @@ -77,8 +78,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT, transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT, transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT, transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT, transferReservesUSD[1]); }); }); @@ -102,9 +103,9 @@ describe("Well Entity: Liquidity Event Tests", () => { let endingBalances = updatedStore.reservesUSD; // Bean balance is still only one unit of BEAN_USD_AMOUNT because the price was cut in half on the second deposit - assert.stringEquals(BEAN_USD_AMOUNT.toString(), endingBalances[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), endingBalances[1].toString()); - assert.stringEquals(BEAN_USD_AMOUNT.plus(WETH_USD_AMOUNT).toString(), updatedStore.totalLiquidityUSD.toString()); + assertBDClose(BEAN_USD_AMOUNT, endingBalances[0]); + assertBDClose(WETH_USD_AMOUNT, endingBalances[1]); + assertBDClose(BEAN_USD_AMOUNT.plus(WETH_USD_AMOUNT), updatedStore.totalLiquidityUSD); }); test("Liquidity Token balance", () => { assert.fieldEquals(WELL_ENTITY_TYPE, WELL.toHexString(), "lpTokenSupply", WELL_LP_AMOUNT.times(BI_2).toString()); @@ -120,8 +121,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT.times(BI_2), transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT, transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")).toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT.times(BigDecimal.fromString("1.5")), transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT, transferReservesUSD[1]); }); }); @@ -144,9 +145,9 @@ describe("Well Entity: Liquidity Event Tests", () => { let updatedStore = loadWell(WELL); let endingBalances = updatedStore.reservesUSD; - assert.stringEquals(BEAN_USD_AMOUNT.toString(), endingBalances[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), endingBalances[1].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BigDecimal.fromString("2")).toString(), updatedStore.totalLiquidityUSD.toString()); + assertBDClose(BEAN_USD_AMOUNT, endingBalances[0]); + assertBDClose(WETH_USD_AMOUNT, endingBalances[1]); + assertBDClose(WETH_USD_AMOUNT.times(BigDecimal.fromString("2")), updatedStore.totalLiquidityUSD); }); test("Liquidity Token balance", () => { assert.fieldEquals(WELL_ENTITY_TYPE, WELL.toHexString(), "lpTokenSupply", WELL_LP_AMOUNT.plus(BI_10).toString()); @@ -162,8 +163,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT, transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT, transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT, transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT, transferReservesUSD[1]); }); }); @@ -188,9 +189,9 @@ describe("Well Entity: Liquidity Event Tests", () => { let endingBalances = updatedStore.reservesUSD; // WETH was doubled from the initial, so the bean price has also doubled - assert.stringEquals(BEAN_USD_AMOUNT.toString(), endingBalances[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), endingBalances[1].toString()); - assert.stringEquals(BEAN_USD_AMOUNT.plus(WETH_USD_AMOUNT).toString(), updatedStore.totalLiquidityUSD.toString()); + assertBDClose(BEAN_USD_AMOUNT, endingBalances[0]); + assertBDClose(WETH_USD_AMOUNT, endingBalances[1]); + assertBDClose(BEAN_USD_AMOUNT.plus(WETH_USD_AMOUNT), updatedStore.totalLiquidityUSD); }); test("Liquidity Token balance", () => { assert.fieldEquals(WELL_ENTITY_TYPE, WELL.toHexString(), "lpTokenSupply", WELL_LP_AMOUNT.plus(BI_10).toString()); @@ -206,8 +207,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT.div(BI_2), transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT, transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.div(BD_2).toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT.div(BD_2), transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT, transferReservesUSD[1]); }); }); @@ -240,8 +241,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT.times(BI_2), transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT.times(BI_2), transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.times(BD_2).toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BD_2).toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT.times(BD_2), transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT.times(BD_2), transferReservesUSD[1]); }); }); @@ -275,8 +276,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT.times(BI_3), transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT.times(BI_2), transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.times(BD_3).toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BD_2).toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT.times(BD_3).truncate(2), transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT.times(BD_2).truncate(2), transferReservesUSD[1]); }); }); @@ -310,8 +311,8 @@ describe("Well Entity: Liquidity Event Tests", () => { assert.bigIntEquals(BEAN_SWAP_AMOUNT.times(BI_2), transferReserves[0]); assert.bigIntEquals(WETH_SWAP_AMOUNT.times(BI_3), transferReserves[1]); - assert.stringEquals(BEAN_USD_AMOUNT.times(BD_2).toString(), transferReservesUSD[0].toString()); - assert.stringEquals(WETH_USD_AMOUNT.times(BD_3).toString(), transferReservesUSD[1].toString()); + assertBDClose(BEAN_USD_AMOUNT.times(BD_2), transferReservesUSD[0]); + assertBDClose(WETH_USD_AMOUNT.times(BD_3), transferReservesUSD[1]); }); }); test("Liquidity Volume Calculation", () => { diff --git a/projects/subgraph-bean/schema.graphql b/projects/subgraph-bean/schema.graphql index 165ab638b..4b85959c4 100644 --- a/projects/subgraph-bean/schema.graphql +++ b/projects/subgraph-bean/schema.graphql @@ -26,16 +26,21 @@ type Token @entity { lastPriceUSD: BigDecimal! } +type Season @entity { + "Season number" + id: ID! + + # Snapshots + beanHourlySnapshots: BeanHourlySnapshot! @derivedFrom(field: "season") + beanDailySnapshots: BeanDailySnapshot! @derivedFrom(field: "season") + poolHourlySnapshots: [PoolHourlySnapshot!]! @derivedFrom(field: "season") + poolDailySnapshots: [PoolDailySnapshot!]! @derivedFrom(field: "season") +} + type Bean @entity { "Contract address of the Bean token" id: Bytes! - "Which chain this Bean is from" - chain: String! - - "Smart contract address of the Beanstalk this Bean is associated with" - beanstalk: String! - "Current supply" supply: BigInt! @@ -63,39 +68,32 @@ type Bean @entity { "Cumulative number of crosses" crosses: Int! - "Detailed cross events during this snapshot" + "Peg crosses information" crossEvents: [BeanCross!]! @derivedFrom(field: "bean") "Last timestamp a cross was seen" lastCross: BigInt! "Last season seen from Beanstalk" - lastSeason: Int! + lastSeason: Season! - "Pools that include this Bean" + "Whitelisted pools having bean" pools: [Pool!]! - "Dewhitelisted pools that include this Bean" + "Dewhitelisted pools having bean" dewhitelistedPools: [Pool!]! - "Hourly snapshot of Bean data" - hourlySnapshot: [BeanHourlySnapshot!]! @derivedFrom(field: "bean") - - "Daily snapshot of Bean data" - dailySnapshot: [BeanDailySnapshot!]! @derivedFrom(field: "bean") + hourlySnapshots: [BeanHourlySnapshot!]! @derivedFrom(field: "bean") + dailySnapshots: [BeanDailySnapshot!]! @derivedFrom(field: "bean") } type BeanHourlySnapshot @entity { "{Token address}-{Season}" id: ID! - "Bean token address" + season: Season! bean: Bean! - - "Current supply" supply: BigInt! - - "Current market cap" marketCap: BigDecimal! "Amount of the supply which is considered Locked Beans (untradeable due to chop rate)" @@ -141,10 +139,7 @@ type BeanHourlySnapshot @entity { deltaCrosses: Int! "Detailed cross events during this snapshot" - crossEvents: [BeanCross!]! @derivedFrom(field: "hourlySnapshot") - - "Season associated with this snapshot" - season: Int! + crossEvents: [BeanCross!]! @derivedFrom(field: "beanHourlySnapshot") "Timestamp this snapshot was updated" timestamp: BigInt! @@ -154,7 +149,10 @@ type BeanHourlySnapshot @entity { } type BeanDailySnapshot @entity { + "{Token address}-{Unix day}" id: ID! + + season: Season! bean: Bean! supply: BigInt! marketCap: BigDecimal! @@ -178,36 +176,11 @@ type BeanDailySnapshot @entity { deltaVolumeUSD: BigDecimal! deltaLiquidityUSD: BigDecimal! deltaCrosses: Int! - crossEvents: [BeanCross!]! @derivedFrom(field: "dailySnapshot") - season: Int! + crossEvents: [BeanCross!]! @derivedFrom(field: "beanDailySnapshot") timestamp: BigInt! blockNumber: BigInt! } -type BeanCross @entity { - id: ID! - bean: Bean! - price: BigDecimal! - blockNumber: BigInt! - timestamp: BigInt! - timeSinceLastCross: BigInt! - above: Boolean! - hourlySnapshot: BeanHourlySnapshot! - dailySnapshot: BeanDailySnapshot! -} - -type PoolCross @entity { - id: ID! - pool: Pool! - price: BigDecimal! - blockNumber: BigInt! - timestamp: BigInt! - timeSinceLastCross: BigInt! - above: Boolean! - hourlySnapshot: PoolHourlySnapshot! - dailySnapshot: PoolDailySnapshot! -} - type Pool @entity { id: Bytes! "The Bean token that is in this pool" @@ -215,7 +188,7 @@ type Pool @entity { "All tokens in this pool" tokens: [Token!]! reserves: [BigInt!]! - lastSeason: Int! + lastSeason: Season! lastPrice: BigDecimal! volume: BigInt! volumeUSD: BigDecimal! @@ -226,12 +199,13 @@ type Pool @entity { crossEvents: [PoolCross!]! @derivedFrom(field: "pool") "Instantaneous deltaB" deltaBeans: BigInt! - hourlySnapshot: [PoolHourlySnapshot!]! @derivedFrom(field: "pool") - dailySnapshot: [PoolDailySnapshot!]! @derivedFrom(field: "pool") + hourlySnapshots: [PoolHourlySnapshot!]! @derivedFrom(field: "pool") + dailySnapshots: [PoolDailySnapshot!]! @derivedFrom(field: "pool") } type PoolHourlySnapshot @entity { id: ID! + season: Season! pool: Pool! reserves: [BigInt!]! lastPrice: BigDecimal! @@ -253,14 +227,14 @@ type PoolHourlySnapshot @entity { deltaVolumeUSD: BigDecimal! deltaLiquidityUSD: BigDecimal! deltaCrosses: Int! - crossEvents: [PoolCross!]! @derivedFrom(field: "hourlySnapshot") - season: Int! + crossEvents: [PoolCross!]! @derivedFrom(field: "poolHourlySnapshot") createdAt: BigInt! updatedAt: BigInt! } type PoolDailySnapshot @entity { id: ID! + season: Season! pool: Pool! reserves: [BigInt!]! lastPrice: BigDecimal! @@ -282,12 +256,37 @@ type PoolDailySnapshot @entity { deltaVolumeUSD: BigDecimal! deltaLiquidityUSD: BigDecimal! deltaCrosses: Int! - crossEvents: [PoolCross!]! @derivedFrom(field: "dailySnapshot") - season: Int! + crossEvents: [PoolCross!]! @derivedFrom(field: "poolDailySnapshot") createdAt: BigInt! updatedAt: BigInt! } +type BeanCross @entity { + id: ID! + bean: Bean! + price: BigDecimal! + blockNumber: BigInt! + timestamp: BigInt! + timeSinceLastCross: BigInt! + above: Boolean! + + beanHourlySnapshot: BeanHourlySnapshot! + beanDailySnapshot: BeanDailySnapshot! +} + +type PoolCross @entity { + id: ID! + pool: Pool! + price: BigDecimal! + blockNumber: BigInt! + timestamp: BigInt! + timeSinceLastCross: BigInt! + above: Boolean! + + poolHourlySnapshot: PoolHourlySnapshot! + poolDailySnapshot: PoolDailySnapshot! +} + # For tracking twa balances as the seasons progress type TwaOracle @entity { id: Bytes! diff --git a/projects/subgraph-bean/src/entities/Bean.ts b/projects/subgraph-bean/src/entities/Bean.ts index b054a92e5..07be7c97a 100644 --- a/projects/subgraph-bean/src/entities/Bean.ts +++ b/projects/subgraph-bean/src/entities/Bean.ts @@ -1,19 +1,15 @@ -import { BigInt, BigDecimal, Address } from "@graphprotocol/graph-ts"; +import { ethereum, BigDecimal, Address } from "@graphprotocol/graph-ts"; import { BEAN_ERC20 } from "../../../subgraph-core/constants/raw/BeanstalkEthConstants"; import { dayFromTimestamp } from "../../../subgraph-core/utils/Dates"; import { ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; import { Bean, BeanDailySnapshot, BeanHourlySnapshot } from "../../generated/schema"; import { getV1Crosses } from "../utils/Cross"; -import { v } from "../utils/constants/Version"; +import { loadOrCreateSeason } from "./Season"; export function loadBean(token: Address): Bean { let bean = Bean.load(token); if (bean == null) { bean = new Bean(token); - const version = v(); - bean.chain = version.chain; - bean.beanstalk = version.protocolAddress.toHexString(); - bean.supply = ZERO_BI; bean.marketCap = ZERO_BD; bean.lockedBeans = ZERO_BI; @@ -24,7 +20,7 @@ export function loadBean(token: Address): Bean { bean.price = BigDecimal.fromString("1.072"); bean.crosses = token == BEAN_ERC20 ? getV1Crosses() : 0; bean.lastCross = ZERO_BI; - bean.lastSeason = token == BEAN_ERC20 ? 6074 : 0; + bean.lastSeason = loadOrCreateSeason(token == BEAN_ERC20 ? 6074 : 0).id; bean.pools = []; bean.dewhitelistedPools = []; bean.save(); @@ -32,11 +28,10 @@ export function loadBean(token: Address): Bean { return bean as Bean; } -export function loadOrCreateBeanHourlySnapshot(token: Address, timestamp: BigInt, season: i32): BeanHourlySnapshot { - let id = token.toHexString() + "-" + season.toString(); +export function loadOrCreateBeanHourlySnapshot(bean: Bean, block: ethereum.Block): BeanHourlySnapshot { + let id = bean.id.toHexString() + "-" + bean.lastSeason; let snapshot = BeanHourlySnapshot.load(id); if (snapshot == null) { - let bean = loadBean(token); snapshot = new BeanHourlySnapshot(id); snapshot.bean = bean.id; snapshot.supply = bean.supply; @@ -56,18 +51,17 @@ export function loadOrCreateBeanHourlySnapshot(token: Address, timestamp: BigInt snapshot.deltaLiquidityUSD = ZERO_BD; snapshot.deltaCrosses = 0; snapshot.season = bean.lastSeason; - snapshot.timestamp = timestamp; - snapshot.blockNumber = ZERO_BI; + snapshot.timestamp = block.timestamp; + snapshot.blockNumber = block.number; snapshot.save(); } return snapshot as BeanHourlySnapshot; } -export function loadOrCreateBeanDailySnapshot(token: Address, timestamp: BigInt): BeanDailySnapshot { - let day = dayFromTimestamp(timestamp).toString(); +export function loadOrCreateBeanDailySnapshot(bean: Bean, block: ethereum.Block): BeanDailySnapshot { + let day = bean.id.toHexString() + "-" + dayFromTimestamp(block.timestamp).toString(); let snapshot = BeanDailySnapshot.load(day); if (snapshot == null) { - let bean = loadBean(token); snapshot = new BeanDailySnapshot(day); snapshot.bean = bean.id; snapshot.supply = bean.supply; @@ -87,8 +81,8 @@ export function loadOrCreateBeanDailySnapshot(token: Address, timestamp: BigInt) snapshot.deltaLiquidityUSD = ZERO_BD; snapshot.deltaCrosses = 0; snapshot.season = bean.lastSeason; - snapshot.timestamp = timestamp; - snapshot.blockNumber = ZERO_BI; + snapshot.timestamp = block.timestamp; + snapshot.blockNumber = block.number; snapshot.save(); } return snapshot as BeanDailySnapshot; diff --git a/projects/subgraph-bean/src/entities/Cross.ts b/projects/subgraph-bean/src/entities/Cross.ts index 7c6cfb832..7577d7130 100644 --- a/projects/subgraph-bean/src/entities/Cross.ts +++ b/projects/subgraph-bean/src/entities/Cross.ts @@ -1,42 +1,41 @@ -import { ethereum, Address } from "@graphprotocol/graph-ts"; -import { dayFromTimestamp, hourFromTimestamp } from "../../../subgraph-core/utils/Dates"; +import { ethereum } from "@graphprotocol/graph-ts"; import { ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; -import { BeanCross, PoolCross } from "../../generated/schema"; +import { Bean, BeanCross, Pool, PoolCross } from "../../generated/schema"; +import { loadOrCreateBeanDailySnapshot, loadOrCreateBeanHourlySnapshot } from "./Bean"; +import { loadOrCreatePoolDailySnapshot, loadOrCreatePoolHourlySnapshot } from "./Pool"; +import { toAddress } from "../../../subgraph-core/utils/Bytes"; -export function loadOrCreateBeanCross(id: i32, bean: Address, block: ethereum.Block): BeanCross { - let cross = BeanCross.load(id.toString()); +export function loadOrCreateBeanCross(bean: Bean, block: ethereum.Block): BeanCross { + let crossID = bean.crosses.toString(); + let cross = BeanCross.load(crossID); if (cross == null) { - let hour = hourFromTimestamp(block.timestamp).toString(); - let day = dayFromTimestamp(block.timestamp).toString(); - cross = new BeanCross(id.toString()); - cross.bean = bean; + cross = new BeanCross(crossID); + cross.bean = bean.id; cross.price = ZERO_BD; cross.blockNumber = block.number; cross.timestamp = block.timestamp; cross.timeSinceLastCross = ZERO_BI; cross.above = false; - cross.hourlySnapshot = hour; - cross.dailySnapshot = day; + cross.beanHourlySnapshot = loadOrCreateBeanHourlySnapshot(bean, block).id; + cross.beanDailySnapshot = loadOrCreateBeanDailySnapshot(bean, block).id; cross.save(); } return cross as BeanCross; } -export function loadOrCreatePoolCross(id: i32, pool: Address, block: ethereum.Block): PoolCross { - let crossID = pool.toHexString() + "-" + id.toString(); +export function loadOrCreatePoolCross(pool: Pool, block: ethereum.Block): PoolCross { + let crossID = pool.id.toHexString() + "-" + pool.crosses.toString(); let cross = PoolCross.load(crossID); if (cross == null) { - let hour = hourFromTimestamp(block.timestamp).toString(); - let day = dayFromTimestamp(block.timestamp).toString(); cross = new PoolCross(crossID); - cross.pool = pool; + cross.pool = pool.id; cross.price = ZERO_BD; cross.blockNumber = block.number; cross.timestamp = block.timestamp; cross.timeSinceLastCross = ZERO_BI; cross.above = false; - cross.hourlySnapshot = hour; - cross.dailySnapshot = day; + cross.poolHourlySnapshot = loadOrCreatePoolHourlySnapshot(toAddress(pool.id), block).id; + cross.poolDailySnapshot = loadOrCreatePoolDailySnapshot(toAddress(pool.id), block).id; cross.save(); } return cross as PoolCross; diff --git a/projects/subgraph-bean/src/entities/Season.ts b/projects/subgraph-bean/src/entities/Season.ts new file mode 100644 index 000000000..bf331b487 --- /dev/null +++ b/projects/subgraph-bean/src/entities/Season.ts @@ -0,0 +1,12 @@ +// Seasons entity aggregates all of the hourly/daily snapshots + +import { Season } from "../../generated/schema"; + +export function loadOrCreateSeason(seasonNumber: u32): Season { + let season = Season.load(seasonNumber.toString()); + if (season == null) { + season = new Season(seasonNumber.toString()); + season.save(); + } + return season as Season; +} diff --git a/projects/subgraph-bean/src/handlers/BeanstalkHandler.ts b/projects/subgraph-bean/src/handlers/BeanstalkHandler.ts index 3f2abad03..996c5d94a 100644 --- a/projects/subgraph-bean/src/handlers/BeanstalkHandler.ts +++ b/projects/subgraph-bean/src/handlers/BeanstalkHandler.ts @@ -2,7 +2,7 @@ import { BigInt } from "@graphprotocol/graph-ts"; import { updateBeanSupplyPegPercent, updateBeanTwa } from "../utils/Bean"; import { Chop, Convert, DewhitelistToken, Shipped, Sunrise, WellOracle } from "../../generated/Bean-ABIs/Reseed"; import { loadBean } from "../entities/Bean"; -import { getTWAPrices, setRawWellReserves, setTwaLast } from "../utils/price/TwaOracle"; +import { setRawWellReserves, setTwaLast } from "../utils/price/TwaOracle"; import { decodeCumulativeWellReserves, setWellTwa } from "../utils/price/WellPrice"; import { updateSeason } from "../utils/legacy/Beanstalk"; import { updatePoolPricesOnCross } from "../utils/Cross"; @@ -10,12 +10,11 @@ import { beanDecimals, getProtocolToken, isUnripe } from "../../../subgraph-core import { v } from "../utils/constants/Version"; import { loadOrCreatePool } from "../entities/Pool"; import { BI_10 } from "../../../subgraph-core/utils/Decimals"; -import { TWAType } from "../utils/price/Types"; import { loadOrCreateTwaOracle } from "../entities/TwaOracle"; // Beanstalk 3 handler here, might not put this in the manifest yet - do not delete. export function handleSunrise(event: Sunrise): void { - updateSeason(event.params.season.toI32(), event.block); + updateSeason(event.params.season.toU32(), event.block); // Fetch price from price contract to capture any non-bean token price movevements // Update the current price regardless of a peg cross diff --git a/projects/subgraph-bean/src/handlers/legacy/LegacyBeanstalkHandler.ts b/projects/subgraph-bean/src/handlers/legacy/LegacyBeanstalkHandler.ts index bcd4a08e5..e03ea5ebf 100644 --- a/projects/subgraph-bean/src/handlers/legacy/LegacyBeanstalkHandler.ts +++ b/projects/subgraph-bean/src/handlers/legacy/LegacyBeanstalkHandler.ts @@ -17,7 +17,7 @@ import { getProtocolToken } from "../../../../subgraph-core/constants/RuntimeCon import { v } from "../../utils/constants/Version"; export function handleSunrise_v1(event: Sunrise): void { - updateSeason(event.params.season.toI32(), event.block); + updateSeason(event.params.season.toU32(), event.block); // V1 logic below let beanToken = getProtocolToken(v(), event.block.number); @@ -53,7 +53,7 @@ export function handleSunrise_v1(event: Sunrise): void { // Replanted -> Reseed export function handleSunrise_v2(event: Sunrise): void { - updateSeason(event.params.season.toI32(), event.block); + updateSeason(event.params.season.toU32(), event.block); // V2 logic below let beanToken = getProtocolToken(v(), event.block.number); diff --git a/projects/subgraph-bean/src/utils/Bean.ts b/projects/subgraph-bean/src/utils/Bean.ts index 96ea2bd50..a5a6b47b2 100644 --- a/projects/subgraph-bean/src/utils/Bean.ts +++ b/projects/subgraph-bean/src/utils/Bean.ts @@ -1,5 +1,5 @@ import { BigDecimal, BigInt, ethereum, Address } from "@graphprotocol/graph-ts"; -import { Pool } from "../../generated/schema"; +import { Bean, Pool } from "../../generated/schema"; import { BEAN_ERC20_V1, BEAN_WETH_V1 } from "../../../subgraph-core/constants/raw/BeanstalkEthConstants"; import { ONE_BD, toDecimal, ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; import { checkBeanCross } from "./Cross"; @@ -12,6 +12,7 @@ import { updateBeanSupplyPegPercent_v1 } from "./legacy/Bean"; import { toAddress } from "../../../subgraph-core/utils/Bytes"; import { getProtocolToken } from "../../../subgraph-core/constants/RuntimeConstants"; import { v } from "./constants/Version"; +import { loadOrCreateSeason } from "../entities/Season"; export function adjustSupply(beanToken: Address, amount: BigInt): void { let bean = loadBean(beanToken); @@ -39,8 +40,8 @@ export function updateBeanValues( bean.liquidityUSD = bean.liquidityUSD.plus(deltaLiquidityUSD); bean.save(); - let beanHourly = loadOrCreateBeanHourlySnapshot(token, block.timestamp, bean.lastSeason); - let beanDaily = loadOrCreateBeanDailySnapshot(token, block.timestamp); + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); beanHourly.volume = bean.volume; beanHourly.volumeUSD = bean.volumeUSD; @@ -67,18 +68,15 @@ export function updateBeanValues( beanDaily.save(); } -export function updateBeanSeason(token: Address, timestamp: BigInt, season: i32): void { - let bean = loadBean(token); - bean.lastSeason = season; +export function updateBeanSeason(bean: Bean, season: u32, block: ethereum.Block): void { + bean.lastSeason = loadOrCreateSeason(season).id; bean.save(); - let beanHourly = loadOrCreateBeanHourlySnapshot(token, timestamp, season); - let beanDaily = loadOrCreateBeanDailySnapshot(token, timestamp); - - beanHourly.season = season; + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); + beanHourly.season = bean.lastSeason; + beanDaily.season = bean.lastSeason; beanHourly.save(); - - beanDaily.season = season; beanDaily.save(); } @@ -158,8 +156,8 @@ export function updateBeanAfterPoolSwap( export function updateInstDeltaB(token: Address, block: ethereum.Block): void { let bean = loadBean(token); - let beanHourly = loadOrCreateBeanHourlySnapshot(token, block.timestamp, bean.lastSeason); - let beanDaily = loadOrCreateBeanDailySnapshot(token, block.timestamp); + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); let cumulativeDeltaB = ZERO_BI; for (let i = 0; i < bean.pools.length; i++) { @@ -177,8 +175,8 @@ export function updateInstDeltaB(token: Address, block: ethereum.Block): void { export function updateBeanTwa(block: ethereum.Block): void { let beanAddress = getProtocolToken(v(), block.number); let bean = loadBean(beanAddress); - let beanHourly = loadOrCreateBeanHourlySnapshot(beanAddress, block.timestamp, bean.lastSeason); - let beanDaily = loadOrCreateBeanDailySnapshot(beanAddress, block.timestamp); + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); let twaDeltaB = ZERO_BI; let weightedTwaPrice = ZERO_BD; diff --git a/projects/subgraph-bean/src/utils/Cross.ts b/projects/subgraph-bean/src/utils/Cross.ts index afc9409cd..e9e4b9fff 100644 --- a/projects/subgraph-bean/src/utils/Cross.ts +++ b/projects/subgraph-bean/src/utils/Cross.ts @@ -14,7 +14,7 @@ export function checkPoolCross(pool: Address, oldPrice: BigDecimal, newPrice: Bi let poolInfo = loadOrCreatePool(pool, block.number); if (oldPrice >= ONE_BD && newPrice < ONE_BD) { - let cross = loadOrCreatePoolCross(poolInfo.crosses, pool, block); + let cross = loadOrCreatePoolCross(poolInfo, block); cross.price = newPrice; cross.timeSinceLastCross = block.timestamp.minus(poolInfo.lastCross); @@ -37,7 +37,7 @@ export function checkPoolCross(pool: Address, oldPrice: BigDecimal, newPrice: Bi poolDaily.save(); return true; } else if (oldPrice < ONE_BD && newPrice >= ONE_BD) { - let cross = loadOrCreatePoolCross(poolInfo.crosses, pool, block); + let cross = loadOrCreatePoolCross(poolInfo, block); cross.price = newPrice; cross.timeSinceLastCross = block.timestamp.minus(poolInfo.lastCross); @@ -67,7 +67,7 @@ export function checkBeanCross(token: Address, oldPrice: BigDecimal, newPrice: B let bean = loadBean(token); if (oldPrice >= ONE_BD && newPrice < ONE_BD) { - let cross = loadOrCreateBeanCross(bean.crosses, token, block); + let cross = loadOrCreateBeanCross(bean, block); cross.price = newPrice; cross.timeSinceLastCross = block.timestamp.minus(bean.lastCross); @@ -78,8 +78,8 @@ export function checkBeanCross(token: Address, oldPrice: BigDecimal, newPrice: B bean.crosses += 1; bean.save(); - let beanHourly = loadOrCreateBeanHourlySnapshot(token, block.timestamp, bean.lastSeason); - let beanDaily = loadOrCreateBeanDailySnapshot(token, block.timestamp); + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); beanHourly.crosses += 1; beanHourly.deltaCrosses += 1; @@ -90,7 +90,7 @@ export function checkBeanCross(token: Address, oldPrice: BigDecimal, newPrice: B beanDaily.save(); return true; } else if (oldPrice < ONE_BD && newPrice >= ONE_BD) { - let cross = loadOrCreateBeanCross(bean.crosses, token, block); + let cross = loadOrCreateBeanCross(bean, block); cross.price = newPrice; cross.timeSinceLastCross = block.timestamp.minus(bean.lastCross); @@ -101,8 +101,8 @@ export function checkBeanCross(token: Address, oldPrice: BigDecimal, newPrice: B bean.crosses += 1; bean.save(); - let beanHourly = loadOrCreateBeanHourlySnapshot(token, block.timestamp, bean.lastSeason); - let beanDaily = loadOrCreateBeanDailySnapshot(token, block.timestamp); + let beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); + let beanDaily = loadOrCreateBeanDailySnapshot(bean, block); beanHourly.crosses += 1; beanHourly.deltaCrosses += 1; diff --git a/projects/subgraph-bean/src/utils/Pool.ts b/projects/subgraph-bean/src/utils/Pool.ts index 3e18489c9..6d01ef36f 100644 --- a/projects/subgraph-bean/src/utils/Pool.ts +++ b/projects/subgraph-bean/src/utils/Pool.ts @@ -5,6 +5,7 @@ import { checkPoolCross } from "./Cross"; import { DeltaBAndPrice } from "./price/Types"; import { loadOrCreatePool, loadOrCreatePoolDailySnapshot, loadOrCreatePoolHourlySnapshot } from "../entities/Pool"; import { toAddress } from "../../../subgraph-core/utils/Bytes"; +import { loadOrCreateSeason } from "../entities/Season"; export function updatePoolValues( poolAddress: Address, @@ -75,9 +76,9 @@ export function updatePoolSeason(poolAddress: Address, season: i32, block: ether let poolHourly = loadOrCreatePoolHourlySnapshot(poolAddress, block); let poolDaily = loadOrCreatePoolDailySnapshot(poolAddress, block); - pool.lastSeason = season; - poolHourly.season = season; - poolDaily.season = season; + pool.lastSeason = loadOrCreateSeason(season).id; + poolHourly.season = pool.lastSeason; + poolDaily.season = pool.lastSeason; pool.save(); poolHourly.save(); diff --git a/projects/subgraph-bean/src/utils/b3-migration/Init.ts b/projects/subgraph-bean/src/utils/b3-migration/Init.ts index f9c67f2f0..9c6d92c02 100644 --- a/projects/subgraph-bean/src/utils/b3-migration/Init.ts +++ b/projects/subgraph-bean/src/utils/b3-migration/Init.ts @@ -1,8 +1,9 @@ -import { ethereum } from "@graphprotocol/graph-ts"; +import { ethereum, store } from "@graphprotocol/graph-ts"; import { BEAN_INITIAL_VALUES } from "../../../cache-builder/results/B3Migration_arb"; import { loadBean, loadOrCreateBeanDailySnapshot, loadOrCreateBeanHourlySnapshot } from "../../entities/Bean"; import { getProtocolToken } from "../../../../subgraph-core/constants/RuntimeConstants"; import { v } from "../constants/Version"; +import { loadOrCreateSeason } from "../../entities/Season"; // Carries over cumulative data from L1 -> L2 subgraph. See cache-builder/beanstalk3.js for the input source. export function init(block: ethereum.Block): void { @@ -13,18 +14,21 @@ export function init(block: ethereum.Block): void { bean.volumeUSD = BEAN_INITIAL_VALUES.volumeUsd; bean.crosses = BEAN_INITIAL_VALUES.crosses; bean.lastCross = BEAN_INITIAL_VALUES.lastCross; - bean.lastSeason = BEAN_INITIAL_VALUES.lastSeason; + bean.lastSeason = loadOrCreateSeason(BEAN_INITIAL_VALUES.lastSeason).id; bean.save(); + + store.remove("Season", "0"); + // No need to initialize supply/price etc as those will be initialized when liquidity is added. // Direct assignment for snapshots is required as to avoid large deltas - const beanHourly = loadOrCreateBeanHourlySnapshot(token, block.timestamp, bean.lastSeason); + const beanHourly = loadOrCreateBeanHourlySnapshot(bean, block); beanHourly.volume = bean.volume; beanHourly.volumeUSD = bean.volumeUSD; beanHourly.crosses = bean.crosses; beanHourly.save(); - const beanDaily = loadOrCreateBeanDailySnapshot(token, block.timestamp); + const beanDaily = loadOrCreateBeanDailySnapshot(bean, block); beanDaily.volume = bean.volume; beanDaily.volumeUSD = bean.volumeUSD; beanDaily.crosses = bean.crosses; diff --git a/projects/subgraph-bean/src/utils/constants/Version.ts b/projects/subgraph-bean/src/utils/constants/Version.ts index 1d8b4a0bc..9ebc2e4f0 100644 --- a/projects/subgraph-bean/src/utils/constants/Version.ts +++ b/projects/subgraph-bean/src/utils/constants/Version.ts @@ -7,7 +7,7 @@ import * as BeanstalkArb from "../../../../subgraph-core/constants/raw/Beanstalk export function handleInitVersion(block: ethereum.Block): void { const versionEntity = new Version("subgraph"); - versionEntity.versionNumber = "3.0.1"; + versionEntity.versionNumber = "3.1.0"; versionEntity.subgraphName = subgraphNameForBlockNumber(block.number); versionEntity.protocolAddress = protocolForBlockNumber(block.number); versionEntity.chain = chainForBlockNumber(block.number); diff --git a/projects/subgraph-bean/src/utils/legacy/Beanstalk.ts b/projects/subgraph-bean/src/utils/legacy/Beanstalk.ts index c46f52d73..5e5bcb278 100644 --- a/projects/subgraph-bean/src/utils/legacy/Beanstalk.ts +++ b/projects/subgraph-bean/src/utils/legacy/Beanstalk.ts @@ -6,11 +6,10 @@ import { toAddress } from "../../../../subgraph-core/utils/Bytes"; import { getProtocolToken } from "../../../../subgraph-core/constants/RuntimeConstants"; import { v } from "../constants/Version"; -export function updateSeason(season: i32, block: ethereum.Block): void { - let beanToken = getProtocolToken(v(), block.number); - updateBeanSeason(beanToken, block.timestamp, season); +export function updateSeason(season: u32, block: ethereum.Block): void { + let bean = loadBean(getProtocolToken(v(), block.number)); + updateBeanSeason(bean, season, block); - let bean = loadBean(beanToken); for (let i = 0; i < bean.pools.length; i++) { updatePoolSeason(toAddress(bean.pools[i]), season, block); } diff --git a/projects/subgraph-bean/tests/Migration.test.ts b/projects/subgraph-bean/tests/Migration.test.ts index de75b2253..88a4b6e7c 100644 --- a/projects/subgraph-bean/tests/Migration.test.ts +++ b/projects/subgraph-bean/tests/Migration.test.ts @@ -18,6 +18,7 @@ describe("Beanstalk 3 Migration", () => { test("Bean entity initialization", () => { assert.fieldEquals("Bean", BEAN_ERC20.toHexString(), "volume", BEAN_INITIAL_VALUES.volume.toString()); + assert.fieldEquals("Bean", BEAN_ERC20.toHexString(), "lastSeason", BEAN_INITIAL_VALUES.lastSeason.toString()); assert.fieldEquals("Bean", BEAN_ERC20.toHexString(), "crosses", BEAN_INITIAL_VALUES.crosses.toString()); assert.fieldEquals( "BeanHourlySnapshot", diff --git a/projects/subgraph-beanstalk/src/entities/Germinating.ts b/projects/subgraph-beanstalk/src/entities/Germinating.ts index faebf889e..b0dda9152 100644 --- a/projects/subgraph-beanstalk/src/entities/Germinating.ts +++ b/projects/subgraph-beanstalk/src/entities/Germinating.ts @@ -1,6 +1,6 @@ -import { Address, BigDecimal, BigInt, ethereum, store } from "@graphprotocol/graph-ts"; -import { ONE_BI, toDecimal, ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; -import { Germinating, PrevFarmerGerminatingEvent } from "../../generated/schema"; +import { Address, BigDecimal, store } from "@graphprotocol/graph-ts"; +import { toDecimal, ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; +import { Germinating } from "../../generated/schema"; export function loadOrCreateGerminating(address: Address, season: i32, isFarmer: boolean): Germinating { const type = germinationSeasonCategory(season); diff --git a/projects/subgraph-core/constants/BeanstalkArb.ts b/projects/subgraph-core/constants/BeanstalkArb.ts index 31da74c39..ff2a00660 100644 --- a/projects/subgraph-core/constants/BeanstalkArb.ts +++ b/projects/subgraph-core/constants/BeanstalkArb.ts @@ -13,7 +13,8 @@ import { BEAN_USDC, BEAN_USDT, AQUIFER, - WELL_STABLE2 + WELL_STABLE2, + WELL_STABLE2_121 } from "./raw/BeanstalkArbConstants"; /// ADDRESSES /// @@ -109,5 +110,5 @@ export function wellFnSupportsRate(wellFnAddress: Address): boolean { } export function isStable2WellFn(wellFnAddress: Address): boolean { - return wellFnAddress == WELL_STABLE2; + return wellFnAddress == WELL_STABLE2 || wellFnAddress == WELL_STABLE2_121; } diff --git a/projects/subgraph-core/constants/raw/BeanstalkArbConstants.ts b/projects/subgraph-core/constants/raw/BeanstalkArbConstants.ts index 1a2a420d7..6fcd088b6 100644 --- a/projects/subgraph-core/constants/raw/BeanstalkArbConstants.ts +++ b/projects/subgraph-core/constants/raw/BeanstalkArbConstants.ts @@ -27,6 +27,8 @@ export const BEANSTALK_PRICE = Address.fromString("0xC218F5a782b0913931DCF502FA2 export const AQUIFER = Address.fromString("0xBA51AAAa8C2f911AE672e783707Ceb2dA6E97521"); export const WELL_CP2 = Address.fromString("0xbA1500c28C8965521f47F17Fc21A7829D6E1343e"); export const WELL_STABLE2 = Address.fromString("0xba150052e11591D0648b17A0E608511874921CBC"); +export const WELL_CP2_121 = Address.fromString("0xBA15000450Bf6d48ec50BD6327A9403E401b72b4"); +export const WELL_STABLE2_121 = Address.fromString("0xba150052e11591D0648b17A0E608511874921CBC"); // Milestone diff --git a/projects/subgraph-core/tests/Assert.ts b/projects/subgraph-core/tests/Assert.ts new file mode 100644 index 000000000..e473bbfdf --- /dev/null +++ b/projects/subgraph-core/tests/Assert.ts @@ -0,0 +1,7 @@ +import { assert } from "matchstick-as/assembly/index"; +import { BigDecimal } from "@graphprotocol/graph-ts"; + +export function assertBDClose(expected: BigDecimal, actual: BigDecimal): void { + const diff = actual.minus(expected); + assert.assertTrue(diff < BigDecimal.fromString("0.1")); +}