From bce460cef500d75773d6f6daa583a1268492ed65 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:35:10 -0700 Subject: [PATCH 1/2] Add misc metadata --- projects/subgraph-bean/schema.graphql | 9 ++ .../subgraph-bean/src/BeanstalkHandler.ts | 1 - .../src/constants/PooledTokens.ts | 96 +++++++++++++++++++ projects/subgraph-bean/src/utils/Bean.ts | 21 +--- projects/subgraph-bean/src/utils/Pool.ts | 6 ++ projects/subgraph-bean/src/utils/Token.ts | 7 +- projects/subgraph-bean/tests/Cross.test.ts | 14 +-- projects/subgraph-bean/tests/Pool.test.ts | 27 ++++++ projects/subgraph-core/utils/Decimals.ts | 29 ++---- 9 files changed, 164 insertions(+), 46 deletions(-) create mode 100644 projects/subgraph-bean/src/constants/PooledTokens.ts create mode 100644 projects/subgraph-bean/tests/Pool.test.ts diff --git a/projects/subgraph-bean/schema.graphql b/projects/subgraph-bean/schema.graphql index 65378d66bc..79720dbbfd 100644 --- a/projects/subgraph-bean/schema.graphql +++ b/projects/subgraph-bean/schema.graphql @@ -2,6 +2,9 @@ type Token @entity { "Smart contract address of the token" id: ID! + "Name of the token, i.e. BEAN, WETH" + name: String! + "Number of decimals" decimals: BigInt! @@ -13,6 +16,9 @@ type Bean @entity { "Contract address of the Bean token" id: ID! + "Which chain this Bean is from" + chain: String! + "Current supply" supply: BigInt! @@ -187,7 +193,10 @@ type PoolCross @entity { type Pool @entity { id: ID! + "The Bean token that is in this pool" bean: Bean! + "All tokens in this pool" + tokens: [Token!]! reserves: [BigInt!]! lastSeason: Int! lastPrice: BigDecimal! diff --git a/projects/subgraph-bean/src/BeanstalkHandler.ts b/projects/subgraph-bean/src/BeanstalkHandler.ts index 32766f630f..2bcb75863a 100644 --- a/projects/subgraph-bean/src/BeanstalkHandler.ts +++ b/projects/subgraph-bean/src/BeanstalkHandler.ts @@ -21,7 +21,6 @@ import { MetapoolOracle, WellOracle } from "../generated/TWAPOracles/BIP37"; import { DeltaBPriceLiquidity } from "./utils/price/Types"; import { setRawWellReserves, setTwaLast } from "./utils/price/TwaOracle"; import { decodeCumulativeWellReserves, setWellTwa } from "./utils/price/WellPrice"; -import { BeanstalkPrice_try_price, getPoolPrice } from "./utils/price/BeanstalkPrice"; import { beanstalkPrice_updatePoolPrices } from "./BlockHandler"; export function handleSunrise(event: Sunrise): void { diff --git a/projects/subgraph-bean/src/constants/PooledTokens.ts b/projects/subgraph-bean/src/constants/PooledTokens.ts new file mode 100644 index 0000000000..6937a83187 --- /dev/null +++ b/projects/subgraph-bean/src/constants/PooledTokens.ts @@ -0,0 +1,96 @@ +import { BigInt, log } from "@graphprotocol/graph-ts"; +import { + BEAN_ERC20, + BEAN_ERC20_V1, + WETH, + CRV3_TOKEN, + LUSD, + BEAN_WETH_V1, + BEAN_3CRV_V1, + BEAN_LUSD_V1, + BEAN_3CRV, + BEAN_WETH_CP2_WELL +} from "../../../subgraph-core/utils/Constants"; + +// Use this mapping to determine which tokens are in each pool. Pools may each follow a distinct interface, +// so a view function shouldn't be used, and a new subgraph build is already required to track a newly whitelisted asset. +export function getTokensForPool(pool: string): string[] { + for (let i = 0; i < poolTokens.length; ++i) { + if (poolTokens[i].pool == pool) { + return poolTokens[i].tokens; + } + } + throw new Error("Pool has not been configured"); +} + +// Name/Decimals are not guaranteed as part of the ERC20 interface, so predefined values are necessary +export function getTokenInfo(token: string): TokenInfo { + for (let i = 0; i < tokens.length; ++i) { + if (tokens[i].address == token) { + return tokens[i].info; + } + } + throw new Error("Token has not been configured"); +} + +class PoolTokens { + pool: string; + tokens: string[]; +} +// WHITELIST: Add new pools here +const poolTokens: PoolTokens[] = [ + { + pool: BEAN_WETH_V1.toHexString(), + tokens: [BEAN_ERC20_V1.toHexString(), WETH.toHexString()] + }, + { + pool: BEAN_3CRV_V1.toHexString(), + tokens: [BEAN_ERC20_V1.toHexString(), CRV3_TOKEN.toHexString()] + }, + { + pool: BEAN_LUSD_V1.toHexString(), + tokens: [BEAN_ERC20_V1.toHexString(), LUSD.toHexString()] + }, + { + pool: BEAN_3CRV.toHexString(), + tokens: [BEAN_ERC20.toHexString(), CRV3_TOKEN.toHexString()] + }, + { + pool: BEAN_WETH_CP2_WELL.toHexString(), + tokens: [BEAN_ERC20.toHexString(), WETH.toHexString()] + } +]; + +class Token { + address: string; + info: TokenInfo; +} + +class TokenInfo { + name: string; + decimals: BigInt; +} + +// WHITELIST: Add new tokens here +const tokens: Token[] = [ + { + address: BEAN_ERC20_V1.toHexString(), + info: { name: "BEAN", decimals: BigInt.fromU32(6) } + }, + { + address: BEAN_ERC20.toHexString(), + info: { name: "BEAN", decimals: BigInt.fromU32(6) } + }, + { + address: WETH.toHexString(), + info: { name: "WETH", decimals: BigInt.fromU32(18) } + }, + { + address: CRV3_TOKEN.toHexString(), + info: { name: "3CRV", decimals: BigInt.fromU32(18) } + }, + { + address: LUSD.toHexString(), + info: { name: "LUSD", decimals: BigInt.fromU32(18) } + } +]; diff --git a/projects/subgraph-bean/src/utils/Bean.ts b/projects/subgraph-bean/src/utils/Bean.ts index ead4f9c236..9c6527f306 100644 --- a/projects/subgraph-bean/src/utils/Bean.ts +++ b/projects/subgraph-bean/src/utils/Bean.ts @@ -20,6 +20,7 @@ export function loadBean(token: string): Bean { let bean = Bean.load(token); if (bean == null) { bean = new Bean(token); + bean.chain = "ethereum"; bean.supply = ZERO_BI; bean.marketCap = ZERO_BD; bean.lockedBeans = ZERO_BI; @@ -38,11 +39,7 @@ export function loadBean(token: string): Bean { return bean as Bean; } -export function loadOrCreateBeanHourlySnapshot( - token: string, - timestamp: BigInt, - season: i32 -): BeanHourlySnapshot { +export function loadOrCreateBeanHourlySnapshot(token: string, timestamp: BigInt, season: i32): BeanHourlySnapshot { let id = token + "-" + season.toString(); let snapshot = BeanHourlySnapshot.load(id); if (snapshot == null) { @@ -188,9 +185,7 @@ export function calcLiquidityWeightedBeanPrice(token: string): BigDecimal { } export function getBeanTokenAddress(blockNumber: BigInt): string { - return blockNumber < BigInt.fromString("15278082") - ? BEAN_ERC20_V1.toHexString() - : BEAN_ERC20.toHexString(); + return blockNumber < BigInt.fromString("15278082") ? BEAN_ERC20_V1.toHexString() : BEAN_ERC20.toHexString(); } export function updateBeanSupplyPegPercent(blockNumber: BigInt): void { @@ -254,15 +249,7 @@ export function updateBeanAfterPoolSwap( } updateBeanSupplyPegPercent(blockNumber); - updateBeanValues( - BEAN_ERC20.toHexString(), - timestamp, - beanPrice, - ZERO_BI, - volumeBean, - volumeUSD, - deltaLiquidityUSD - ); + updateBeanValues(BEAN_ERC20.toHexString(), timestamp, beanPrice, ZERO_BI, volumeBean, volumeUSD, deltaLiquidityUSD); checkBeanCross(BEAN_ERC20.toHexString(), timestamp, blockNumber, oldBeanPrice, beanPrice); } } diff --git a/projects/subgraph-bean/src/utils/Pool.ts b/projects/subgraph-bean/src/utils/Pool.ts index 0995dae9c1..1b57b8eccf 100644 --- a/projects/subgraph-bean/src/utils/Pool.ts +++ b/projects/subgraph-bean/src/utils/Pool.ts @@ -5,6 +5,8 @@ import { emptyBigIntArray, ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils import { getBeanTokenAddress, loadBean, updateInstDeltaB } from "./Bean"; import { checkPoolCross } from "./Cross"; import { DeltaBAndPrice } from "./price/Types"; +import { getTokensForPool } from "../constants/PooledTokens"; +import { loadOrCreateToken } from "./Token"; export function loadOrCreatePool(poolAddress: string, blockNumber: BigInt): Pool { let pool = Pool.load(poolAddress); @@ -13,6 +15,10 @@ export function loadOrCreatePool(poolAddress: string, blockNumber: BigInt): Pool let bean = loadBean(beanAddress); pool = new Pool(poolAddress); + pool.tokens = getTokensForPool(poolAddress); + for (let i = 0; i < pool.tokens.length; ++i) { + loadOrCreateToken(pool.tokens[i]); + } pool.bean = beanAddress; pool.reserves = emptyBigIntArray(2); pool.lastSeason = bean.lastSeason; diff --git a/projects/subgraph-bean/src/utils/Token.ts b/projects/subgraph-bean/src/utils/Token.ts index 8dae64b45d..a371df1909 100644 --- a/projects/subgraph-bean/src/utils/Token.ts +++ b/projects/subgraph-bean/src/utils/Token.ts @@ -1,12 +1,15 @@ -import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; +import { BigDecimal } from "@graphprotocol/graph-ts"; import { Token } from "../../generated/schema"; import { ZERO_BD } from "../../../subgraph-core/utils/Decimals"; +import { getTokenInfo } from "../constants/PooledTokens"; export function loadOrCreateToken(address: string): Token { let token = Token.load(address); if (token == null) { + const tokenInfo = getTokenInfo(address); token = new Token(address); - token.decimals = BigInt.fromString("18"); + token.name = tokenInfo.name; + token.decimals = tokenInfo.decimals; token.lastPriceUSD = ZERO_BD; token.save(); } diff --git a/projects/subgraph-bean/tests/Cross.test.ts b/projects/subgraph-bean/tests/Cross.test.ts index 19ba8979ce..d919409153 100644 --- a/projects/subgraph-bean/tests/Cross.test.ts +++ b/projects/subgraph-bean/tests/Cross.test.ts @@ -8,7 +8,7 @@ import { mockBlock } from "../../subgraph-core/tests/event-mocking/Block"; import { mockPreReplantETHPrice, simpleMockPrice } from "../../subgraph-core/tests/event-mocking/Price"; import { BEAN_3CRV_V1, BEAN_ERC20, BEAN_ERC20_V1, BEAN_WETH_CP2_WELL, BEAN_WETH_V1 } from "../../subgraph-core/utils/Constants"; -import { BD_10, ONE_BD, ONE_BI, toDecimal, ZERO_BD, ZERO_BI } from "../../subgraph-core/utils/Decimals"; +import { BD_10, BigDecimal_round, ONE_BD, ONE_BI, toDecimal, ZERO_BD, ZERO_BI } from "../../subgraph-core/utils/Decimals"; import { loadBean } from "../src/utils/Bean"; import { getPreReplantPriceETH, constantProductPrice, uniswapV2Reserves } from "../src/utils/price/UniswapPrice"; @@ -75,8 +75,8 @@ describe("Peg Crosses", () => { const reserves = uniswapV2Reserves(BEAN_WETH_V1); const ethPriceNow = getPreReplantPriceETH(); const newPrice = constantProductPrice(toDecimal(reserves[1]), toDecimal(reserves[0], 18), ethPriceNow); - log.info("expected | actual {} | {}", [beanPrice.toString(), newPrice.truncate(4).toString()]); - assert.assertTrue(beanPrice.equals(newPrice)); + // log.info("expected | actual {} | {}", [beanPrice.toString(), newPrice.truncate(4).toString()]); + assert.assertTrue(beanPrice.equals(newPrice.truncate(4))); const beanPrice2 = BigDecimal.fromString("0.7652"); const liquidity2 = BigDecimal.fromString("1234567"); @@ -86,10 +86,10 @@ describe("Peg Crosses", () => { const ethPriceNow2 = getPreReplantPriceETH(); const newPrice2 = constantProductPrice(toDecimal(reserves2[1]), toDecimal(reserves2[0], 18), ethPriceNow2); const newLiquidity2 = toDecimal(reserves2[0], 18).times(ethPriceNow2).times(BigDecimal.fromString("2")); - log.info("expected | actual {} | {}", [beanPrice2.toString(), newPrice2.truncate(4).toString()]); - assert.assertTrue(beanPrice2.equals(newPrice2)); - log.info("expected | actual {} | {}", [liquidity2.truncate(0).toString(), newLiquidity2.truncate(0).toString()]); - // assert.assertTrue(liquidity2.truncate(0).equals(newLiquidity2.truncate(0))); + // log.info("expected | actual {} | {}", [beanPrice2.toString(), newPrice2.truncate(4).toString()]); + assert.assertTrue(beanPrice2.equals(newPrice2.truncate(4))); + // log.info("expected | actual {} | {}", [liquidity2.truncate(2).toString(), newLiquidity2.truncate(2).toString()]); + assert.assertTrue(BigDecimal_round(liquidity2).equals(BigDecimal_round(newLiquidity2))); }); test("UniswapV2/Bean cross above", () => { diff --git a/projects/subgraph-bean/tests/Pool.test.ts b/projects/subgraph-bean/tests/Pool.test.ts new file mode 100644 index 0000000000..21e0a3684c --- /dev/null +++ b/projects/subgraph-bean/tests/Pool.test.ts @@ -0,0 +1,27 @@ +import { afterEach, clearStore, describe, assert, test } from "matchstick-as/assembly/index"; +import { loadOrCreateToken } from "../src/utils/Token"; +import { BEAN_3CRV, BEAN_ERC20, BEAN_ERC20_V1, BEAN_WETH_V1, CRV3_TOKEN, WETH } from "../../subgraph-core/utils/Constants"; +import { BigInt } from "@graphprotocol/graph-ts"; +import { loadOrCreatePool } from "../src/utils/Pool"; + +describe("Token", () => { + afterEach(() => { + clearStore(); + }); + + test("Pool and its tokens are assigned appropriate metadata", () => { + const pool = loadOrCreatePool(BEAN_WETH_V1.toHexString(), BigInt.fromU32(14500000)); + assert.stringEquals(BEAN_ERC20_V1.toHexString(), pool.tokens[0]); + assert.stringEquals(WETH.toHexString(), pool.tokens[1]); + + assert.fieldEquals("Token", BEAN_ERC20_V1.toHexString(), "decimals", "6"); + assert.fieldEquals("Token", WETH.toHexString(), "decimals", "18"); + + const pool2 = loadOrCreatePool(BEAN_3CRV.toHexString(), BigInt.fromU32(17500000)); + assert.stringEquals(BEAN_ERC20.toHexString(), pool2.tokens[0]); + assert.stringEquals(CRV3_TOKEN.toHexString(), pool2.tokens[1]); + + assert.fieldEquals("Token", BEAN_ERC20.toHexString(), "decimals", "6"); + assert.fieldEquals("Token", CRV3_TOKEN.toHexString(), "decimals", "18"); + }); +}); diff --git a/projects/subgraph-core/utils/Decimals.ts b/projects/subgraph-core/utils/Decimals.ts index 3ff8e3ec02..3a2253dc9f 100644 --- a/projects/subgraph-core/utils/Decimals.ts +++ b/projects/subgraph-core/utils/Decimals.ts @@ -6,9 +6,7 @@ export const ZERO_BI = BigInt.fromI32(0); export const ONE_BI = BigInt.fromI32(1); export const BI_6 = BigInt.fromI32(6); export const BI_10 = BigInt.fromI32(10); -export const BI_MAX = BigInt.fromUnsignedBytes( - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") -); +export const BI_MAX = BigInt.fromUnsignedBytes(Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); export const ZERO_BD = BigDecimal.fromString("0"); export const ONE_BD = BigDecimal.fromString("1"); export const BD_10 = BigDecimal.fromString("10"); @@ -27,10 +25,7 @@ export function pow(base: BigDecimal, exponent: number): BigDecimal { return result; } -export function sqrt( - value: BigDecimal, - tolerance: BigDecimal = BigDecimal.fromString("0.0000001") -): BigDecimal { +export function sqrt(value: BigDecimal, tolerance: BigDecimal = BigDecimal.fromString("0.0000001")): BigDecimal { if (value.equals(ZERO_BD)) { return ZERO_BD; } @@ -46,10 +41,8 @@ export function sqrt( // Check if the difference is within the tolerance level if ( lastX.minus(x).equals(ZERO_BD) || - (lastX.minus(x).toString().startsWith("-") && - lastX.minus(x).toString().substring(1) < tolerance.toString()) || - (!lastX.minus(x).toString().startsWith("-") && - lastX.minus(x).toString() < tolerance.toString()) + (lastX.minus(x).toString().startsWith("-") && lastX.minus(x).toString().substring(1) < tolerance.toString()) || + (!lastX.minus(x).toString().startsWith("-") && lastX.minus(x).toString() < tolerance.toString()) ) { break; } @@ -68,9 +61,7 @@ export function toDecimal(value: BigInt, decimals: number = DEFAULT_DECIMALS): B export function toBigInt(value: BigDecimal, decimals: number = DEFAULT_DECIMALS): BigInt { let precision = 10 ** decimals; - return BigInt.fromString( - value.times(BigDecimal.fromString(precision.toString())).truncate(0).toString() - ); + return BigInt.fromString(value.times(BigDecimal.fromString(precision.toString())).truncate(0).toString()); } export function emptyBigIntArray(length: i32): BigInt[] { @@ -107,10 +98,10 @@ export function getBigDecimalArrayTotal(detail: BigDecimal[]): BigDecimal { return total; } -export function BigDecimal_isClose( - value: BigDecimal, - target: BigDecimal, - window: BigDecimal -): boolean { +export function BigDecimal_isClose(value: BigDecimal, target: BigDecimal, window: BigDecimal): boolean { return target.minus(window) < value && value < target.plus(window); } + +export function BigDecimal_round(value: BigDecimal): BigDecimal { + return value.plus(BigDecimal.fromString("0.5")).truncate(0); +} From 95a1e62c20fb105e305ea14010520a211add9ba8 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:49:51 -0700 Subject: [PATCH 2/2] Add beanstalk --- projects/subgraph-bean/schema.graphql | 5 ++++- projects/subgraph-bean/src/utils/Bean.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/projects/subgraph-bean/schema.graphql b/projects/subgraph-bean/schema.graphql index 79720dbbfd..f740f8b026 100644 --- a/projects/subgraph-bean/schema.graphql +++ b/projects/subgraph-bean/schema.graphql @@ -8,7 +8,7 @@ type Token @entity { "Number of decimals" decimals: BigInt! - "Last USD price calculated" + "Last USD price calculated. Isn't calculated for all tokens, in those cases will be zero." lastPriceUSD: BigDecimal! } @@ -19,6 +19,9 @@ type Bean @entity { "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! diff --git a/projects/subgraph-bean/src/utils/Bean.ts b/projects/subgraph-bean/src/utils/Bean.ts index 9c6527f306..cd3c7e70bf 100644 --- a/projects/subgraph-bean/src/utils/Bean.ts +++ b/projects/subgraph-bean/src/utils/Bean.ts @@ -7,7 +7,8 @@ import { BEAN_WETH_V1, BEAN_WETH_CP2_WELL, BEAN_3CRV_V1, - BEAN_LUSD_V1 + BEAN_LUSD_V1, + BEANSTALK } from "../../../subgraph-core/utils/Constants"; import { dayFromTimestamp, hourFromTimestamp } from "../../../subgraph-core/utils/Dates"; import { ONE_BD, toDecimal, ZERO_BD, ZERO_BI } from "../../../subgraph-core/utils/Decimals"; @@ -21,6 +22,7 @@ export function loadBean(token: string): Bean { if (bean == null) { bean = new Bean(token); bean.chain = "ethereum"; + bean.beanstalk = BEANSTALK.toHexString(); bean.supply = ZERO_BI; bean.marketCap = ZERO_BD; bean.lockedBeans = ZERO_BI;