Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bean Subgraph] Metadata updates #927

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion projects/subgraph-bean/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ 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!

"Last USD price calculated"
"Last USD price calculated. Isn't calculated for all tokens, in those cases will be zero."
lastPriceUSD: BigDecimal!
}

type Bean @entity {
"Contract address of the Bean token"
id: ID!

"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!

Expand Down Expand Up @@ -187,7 +196,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!
Expand Down
1 change: 0 additions & 1 deletion projects/subgraph-bean/src/BeanstalkHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
96 changes: 96 additions & 0 deletions projects/subgraph-bean/src/constants/PooledTokens.ts
Original file line number Diff line number Diff line change
@@ -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) }
}
];
25 changes: 7 additions & 18 deletions projects/subgraph-bean/src/utils/Bean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -20,6 +21,8 @@ export function loadBean(token: string): Bean {
let bean = Bean.load(token);
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;
Expand All @@ -38,11 +41,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) {
Expand Down Expand Up @@ -188,9 +187,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 {
Expand Down Expand Up @@ -254,15 +251,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);
}
}
Expand Down
6 changes: 6 additions & 0 deletions projects/subgraph-bean/src/utils/Pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions projects/subgraph-bean/src/utils/Token.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Expand Down
14 changes: 7 additions & 7 deletions projects/subgraph-bean/tests/Cross.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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");
Expand All @@ -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", () => {
Expand Down
27 changes: 27 additions & 0 deletions projects/subgraph-bean/tests/Pool.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
29 changes: 10 additions & 19 deletions projects/subgraph-core/utils/Decimals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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[] {
Expand Down Expand Up @@ -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);
}
Loading