├── .eslintignore ├── subgraphs ├── shared │ ├── src │ │ ├── Dummy.ts │ │ ├── price │ │ │ ├── Uniswapv2.ts │ │ │ ├── PriceHandlerStablecoin.ts │ │ │ ├── PriceHandlerRemapping.ts │ │ │ ├── PriceHandlerCustomMapping.ts │ │ │ └── PriceHandler.ts │ │ ├── contracts │ │ │ ├── ContractLookup.ts │ │ │ ├── sOlympus.ts │ │ │ └── TokenDefinition.ts │ │ ├── Constants.ts │ │ ├── utils │ │ │ ├── StringHelper.ts │ │ │ ├── LendingMarketDeployment.ts │ │ │ ├── DateHelper.ts │ │ │ ├── ArrayHelper.ts │ │ │ ├── TokensForChain.ts │ │ │ ├── Decimals.ts │ │ │ ├── TokenNative.ts │ │ │ └── TokenSupplyHelper.ts │ │ └── supply │ │ │ └── OhmCalculations.ts │ ├── tests │ │ ├── ERC20Helper.ts │ │ └── price │ │ │ ├── PriceHandlerCustomMapping.test.ts │ │ │ ├── PriceHandlerCustom.ts │ │ │ ├── PriceHandlerERC4626.test.ts │ │ │ └── PriceRouter.test.ts │ └── subgraph.yaml ├── arbitrum │ ├── tests │ │ ├── dummy.test.ts │ │ ├── priceChainlink.test.ts │ │ ├── chainlink.ts │ │ └── priceLookup.test.ts │ ├── config.json │ ├── CHANGELOG.md │ ├── src │ │ ├── price │ │ │ ├── PriceChainlink.ts │ │ │ └── PriceBase.ts │ │ ├── contracts │ │ │ ├── Sentiment.ts │ │ │ ├── Silo.ts │ │ │ └── TreasureMining.ts │ │ └── treasury │ │ │ ├── Assets.ts │ │ │ ├── TokenBalances.ts │ │ │ └── OwnedLiquidity.ts │ └── subgraph.yaml ├── base │ ├── tests │ │ └── dummy.test.ts │ ├── config.json │ ├── CHANGELOG.md │ ├── subgraph.yaml │ └── src │ │ ├── price │ │ ├── PriceChainlink.ts │ │ ├── PriceBase.ts │ │ └── PriceLookup.ts │ │ ├── treasury │ │ ├── Assets.ts │ │ └── TokenBalances.ts │ │ └── contracts │ │ └── Contracts.ts ├── fantom │ ├── tests │ │ └── dummy.test.ts │ ├── CHANGELOG.md │ ├── config.json │ ├── src │ │ ├── contracts │ │ │ ├── ProtocolAddresses.ts │ │ │ └── Contracts.ts │ │ ├── treasury │ │ │ ├── Assets.ts │ │ │ ├── TokenBalances.ts │ │ │ └── OwnedLiquidity.ts │ │ └── price │ │ │ └── PriceLookup.ts │ └── subgraph.yaml ├── polygon │ ├── tests │ │ └── dummy.test.ts │ ├── config.json │ ├── CHANGELOG.md │ ├── src │ │ ├── treasury │ │ │ ├── Assets.ts │ │ │ ├── TokenBalances.ts │ │ │ └── OwnedLiquidity.ts │ │ ├── price │ │ │ └── PriceLookup.ts │ │ └── contracts │ │ │ └── Contracts.ts │ └── subgraph.yaml ├── price-snapshot │ ├── config.json │ ├── README.md │ ├── CHANGELOG.md │ ├── src │ │ └── helpers │ │ │ └── Math.ts │ ├── subgraph.yaml │ ├── schema.graphql │ └── tests │ │ └── Math.test.ts ├── ethereum │ ├── config.json │ ├── src │ │ ├── utils │ │ │ ├── Dates.ts │ │ │ ├── PairHandler.ts │ │ │ ├── GOhmCalculations.ts │ │ │ ├── PriceChainlink.ts │ │ │ ├── Silo.ts │ │ │ └── TreasuryCalculations.ts │ │ ├── GnosisAuction.ts │ │ ├── contracts │ │ │ ├── ERC20.ts │ │ │ ├── CoolerLoansClearinghouse.ts │ │ │ └── CoolerLoansV2Monocooler.ts │ │ └── protocolMetrics │ │ │ ├── TreasuryMetrics.ts │ │ │ └── Rebase.ts │ ├── tests │ │ ├── .docker │ │ │ └── Dockerfile │ │ ├── walletHelper.ts │ │ ├── tokenStablecoins.test.ts │ │ ├── bophadesHelper.ts │ │ ├── erc20Helper.ts │ │ ├── decimals.test.ts │ │ └── chainlink.ts │ └── abis │ │ ├── OlympusStakingV2Helper.json │ │ └── BondingCalculator.json └── berachain │ ├── config.json │ ├── CHANGELOG.md │ ├── src │ ├── price │ │ ├── PriceChainlink.ts │ │ ├── PriceBase.ts │ │ └── PriceLookup.ts │ ├── treasury │ │ ├── Assets.ts │ │ └── TokenBalances.ts │ └── contracts │ │ ├── Contracts.ts │ │ └── LiquidityConstants.ts │ └── subgraph.yaml ├── .gitattributes ├── .prettierrc.json ├── tsconfig.ts-prune.json ├── assets └── json-to-csv.gif ├── tsconfig.json ├── bin └── subgraph │ ├── tsconfig.json │ └── src │ ├── helpers │ ├── fs.ts │ ├── process.ts │ ├── number.ts │ ├── subgraphConfig.ts │ └── metrics.ts │ ├── networkHandler.ts │ └── subgraphs │ ├── shared │ ├── results.ts │ └── index.ts │ └── ethereum │ ├── index.ts │ └── results.ts ├── .editorconfig ├── .env.sample ├── matchstick.template.yaml ├── .prettierignore ├── .vscode ├── settings.json └── extensions.json ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .eslintrc.json └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/generated 2 | -------------------------------------------------------------------------------- /subgraphs/shared/src/Dummy.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /subgraphs/base/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /subgraphs/fantom/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /subgraphs/polygon/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /subgraphs/shared/src/price/Uniswapv2.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.ts-prune.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["src/utils/ProtocolMetrics.ts"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/json-to-csv.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OlympusDAO/olympus-protocol-metrics-subgraph/HEAD/assets/json-to-csv.gif -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@graphprotocol/graph-ts/tsconfig.json", 3 | "include": ["./**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /bin/subgraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es2017" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /subgraphs/fantom/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # protocol-metrics-fantom 2 | 3 | ## v1.0.4 (2023-10-09) 4 | 5 | - Shift to polling block handler 6 | - Deploy on Graph Protocol Decentralized Network 7 | -------------------------------------------------------------------------------- /subgraphs/base/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmWj7CDe7VivLqX49g6nXjni8w3XFokY5Pwiau78xyox9p", 3 | "org": "olympusdao", 4 | "name": "protocol-metrics-base", 5 | "version": "1.0.9" 6 | } 7 | -------------------------------------------------------------------------------- /subgraphs/fantom/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmNUJtrE5Hiwj5eBeF5gSubY2vhuMdjaZnZsaq6vVY2aba", 3 | "version": "1.0.4", 4 | "org": "olympusdao", 5 | "name": "protocol-metrics-fantom" 6 | } -------------------------------------------------------------------------------- /subgraphs/polygon/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmdDUpqEzfKug1ER6HWM8c7U6wf3wtEtRBvXV7LkVoBi9f", 3 | "org": "olympusdao", 4 | "name": "protocol-metrics-polygon", 5 | "version": "1.1.1" 6 | } -------------------------------------------------------------------------------- /subgraphs/price-snapshot/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmbjnsGDqVH3UgUAYjvCKEeoShHMEWFi3czmXyh2gqDPFP", 3 | "org": "olympusdao", 4 | "name": "price-snapshot", 5 | "version": "1.3.2" 6 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmNQfMN2GjnGYx2mGo92gAc7z47fMbTMRR9M1gGEUjLZHX", 3 | "org": "olympusdao", 4 | "name": "protocol-metrics-arbitrum", 5 | "version": "1.7.9" 6 | } -------------------------------------------------------------------------------- /subgraphs/ethereum/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmXVhEmBrM69CqyE1Urw6jCMsXEGJKH3s8fnro6xnqeXwf", 3 | "org": "olympusdao", 4 | "name": "olympus-protocol-metrics", 5 | "version": "5.8.3" 6 | } 7 | -------------------------------------------------------------------------------- /subgraphs/berachain/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QmUP849xLAPUbDAA3apJe2W2VZhx3AtAgcavoXfjVWGAZX", 3 | "org": "olympusdao", 4 | "name": "protocol-metrics-berachain", 5 | "version": "1.3.6" 6 | } 7 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | GRAPH_STUDIO_TOKEN=CHANGEME 2 | GRAPH_TOKEN_arbitrum=CHANGEME 3 | GRAPH_TOKEN_ethereum=CHANGEME 4 | GRAPH_TOKEN_fantom=CHANGEME 5 | GRAPH_TOKEN_polygon=CHANGEME 6 | GRAPH_TOKEN_price_snapshot=CHANGEME 7 | -------------------------------------------------------------------------------- /subgraphs/shared/src/contracts/ContractLookup.ts: -------------------------------------------------------------------------------- 1 | export type ContractNameLookup = ( 2 | tokenAddress: string, 3 | suffix?: string | null, 4 | abbreviation?: string | null // Avoid last comma 5 | ) => string; 6 | -------------------------------------------------------------------------------- /subgraphs/polygon/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # protocol-metrics-polygon 2 | 3 | ## v1.1.0 (2023-10-09) 4 | 5 | - Implemented polling block handler for faster (hopefully) indexing 6 | - Deployment on Graph Protocol Decentralized Network 7 | -------------------------------------------------------------------------------- /matchstick.template.yaml: -------------------------------------------------------------------------------- 1 | # Matchstick only supports matchstick.yaml in the current directory, so we need to generate from a template 2 | testsFolder: ./subgraphs/{{ subgraph }}/tests/ 3 | manifestPath: ./subgraphs/{{ subgraph }}/subgraph.yaml 4 | -------------------------------------------------------------------------------- /bin/subgraph/src/helpers/fs.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from "fs" 2 | 3 | export const getDirectories = (path: string, includePath = false): string[] => { 4 | return readdirSync(path, { withFileTypes: true }).filter(f => f.isDirectory()).map(f => `${includePath ? `${path}/` : ""}${f.name}`); 5 | } 6 | -------------------------------------------------------------------------------- /subgraphs/shared/src/Constants.ts: -------------------------------------------------------------------------------- 1 | export const ERC20_SOHM_V2 = "0x04f2694c8fcee23e8fd0dfea1d4f5bb8c352111f".toLowerCase(); 2 | export const ERC20_SOHM_V2_BLOCK = "12622596"; 3 | export const ERC20_SOHM_V3 = "0x04906695D6D12CF5459975d7C3C03356E4Ccd460".toLowerCase(); 4 | export const ERC20_SOHM_V3_BLOCK = "13806000"; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | networks/arbitrum/src/contracts/Constants.ts 2 | networks/arbitrum/src/price/PriceLookup.ts 3 | networks/ethereum/src/utils/Constants.ts 4 | networks/fantom/src/contracts/Constants.ts 5 | networks/fantom/src/price/PriceLookup.ts 6 | networks/polygon/src/contracts/Constants.ts 7 | networks/polygon/src/price/PriceLookup.ts 8 | networks/shared/src/price/PriceHandler.ts 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.alwaysShowStatus": true, 3 | "eslint.packageManager": "yarn", 4 | "eslint.format.enable": true, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit" 7 | }, 8 | "cSpell.words": [ 9 | "arbitrum", 10 | "assemblyscript", 11 | "Chainlink", 12 | "deadcode", 13 | "graphprotocol" 14 | ], 15 | } -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/StringHelper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Determines if two addresses (in string format) are equal. 3 | * 4 | * This ensures that: 5 | * - Both addresses are lowercase 6 | * - The correct equality test (==) is used: https://github.com/AssemblyScript/assemblyscript/issues/621 7 | */ 8 | export const addressesEqual = (one: string, two: string): boolean => { 9 | return one.toLowerCase() == two.toLowerCase(); 10 | }; 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "gruntfuggly.todo-tree", 4 | "dbaeumer.vscode-eslint", 5 | "mquandalle.graphql", 6 | "graphql.vscode-graphql", 7 | "davidanson.vscode-markdownlint", 8 | "rvest.vs-code-prettier-eslint", 9 | "redhat.vscode-yaml", 10 | "shd101wyy.markdown-preview-enhanced", 11 | "streetsidesoftware.code-spell-checker" 12 | ] 13 | } -------------------------------------------------------------------------------- /subgraphs/fantom/src/contracts/ProtocolAddresses.ts: -------------------------------------------------------------------------------- 1 | import { CROSS_CHAIN_FANTOM, DAO_WALLET } from "../../../shared/src/Wallets"; 2 | 3 | /** 4 | * The addresses relevant on Fantom. 5 | */ 6 | export const FANTOM_PROTOCOL_ADDRESSES: string[] = [ 7 | CROSS_CHAIN_FANTOM, // Everything is contained in one wallet - no need to iterate over other addresses. 8 | DAO_WALLET, // Just in case there is a snapshot during a bridging action 9 | ]; 10 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/Dates.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | export function dayFromTimestamp(timestamp: BigInt): string { 4 | const day_ts = timestamp.toI32() - (timestamp.toI32() % 86400); 5 | return day_ts.toString(); 6 | } 7 | 8 | export function hourFromTimestamp(timestamp: BigInt): string { 9 | const day_ts = timestamp.toI32() - (timestamp.toI32() % 3600); 10 | return day_ts.toString(); 11 | } 12 | -------------------------------------------------------------------------------- /subgraphs/price-snapshot/README.md: -------------------------------------------------------------------------------- 1 | # Price Snapshot Subgraph 2 | 3 | This subgraph tracks the price of OHM and gOHM (currently every hour), along with the volatility related to major crypto assets. 4 | 5 | ## Users 6 | 7 | The subgraph is used by the following: 8 | 9 | - [RBS Discord Alerts](https://github.com/OlympusDAO/rbs-discord-alerts/) 10 | 11 | Care should be taken when updating/modifying this subgraph to avoid breaking any downstream users. 12 | -------------------------------------------------------------------------------- /bin/subgraph/src/helpers/process.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "child_process"; 2 | 3 | export const spawnProcess = ( 4 | command: string, 5 | onExit: (code: number | null, signal: NodeJS.Signals | null) => void, 6 | ): void => { 7 | const process = spawn(command, { shell: true }); 8 | 9 | process.stdout.on("data", (data: Buffer) => { 10 | console.info(data.toString()); 11 | }); 12 | 13 | process.stderr.on("data", (data: Buffer) => { 14 | console.error(data.toString()); 15 | }); 16 | 17 | process.on("exit", onExit); 18 | }; 19 | -------------------------------------------------------------------------------- /subgraphs/base/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Base Subgraph Changelog 2 | 3 | ## 1.0.9 (2025-01-28) 4 | 5 | - Removes tracking of USDS, sUSDS and OHM-sUSDS LP 6 | - Adds tracking of USDC and OHM-USDC LP 7 | - Fixes issues with underlying token balances for Uniswap V3 positions 8 | 9 | ## 1.0.2 (2025-01-10) 10 | 11 | - Adds price handler for ERC4626 tokens 12 | - Adds price handler for Uniswap V3 (specifically, underlying token balances) 13 | - Adds tracking of USDS, sUSDS and OHM-sUSDS LP 14 | 15 | ## 0.0.1 (2024-06-04) 16 | 17 | - Adds POL in Uniswap V2 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /bin/subgraph/src/helpers/number.ts: -------------------------------------------------------------------------------- 1 | const DIFF_THRESHOLD = 1000; 2 | 3 | export const valuesEqual = ( 4 | value1: number, 5 | value2: number, 6 | threshold = DIFF_THRESHOLD, 7 | ): boolean => { 8 | return value1 - value2 < threshold && value2 - value1 < threshold; 9 | }; 10 | 11 | export const formatCurrency = (value: number, decimals = 0): string => { 12 | return value.toLocaleString("en-US", { 13 | style: "currency", 14 | currency: "USD", 15 | maximumFractionDigits: decimals, 16 | }); 17 | }; 18 | 19 | export const formatNumber = (value: number, decimals = 0): string => { 20 | return value.toLocaleString("en-US", { maximumFractionDigits: decimals }); 21 | }; 22 | -------------------------------------------------------------------------------- /subgraphs/price-snapshot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.2 (2023-11-29) 4 | 5 | - Re-deployment to benefit from changes to OHM protocol-owned liquidity 6 | 7 | ## 1.2.0 (2023-07-18) 8 | 9 | - Improve the indexing speed by shifting to using an event handler for every hour, instead of a block handler. 10 | 11 | ## 1.1.2 (2023-05-13) 12 | 13 | - Deployed to Subgraph Studio/Decentralized Network 14 | - Amend to support caching in Ethereum protocol-metrics subgraph (which this uses for price resolution) 15 | 16 | ## 1.0.9 (2023-01-25) 17 | 18 | - Initial release as a separate subgraph from protocol-metrics. 19 | - Adds 1d delta and 30d volatility (standard deviation) between OHM and USD. 20 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/x86_64 ubuntu:22.04 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | ENV ARGS="" 6 | 7 | RUN apt update \ 8 | && apt install -y sudo curl postgresql postgresql-contrib 9 | 10 | RUN curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - \ 11 | && sudo apt-get install -y nodejs 12 | 13 | RUN curl -OL https://github.com/LimeChain/matchstick/releases/download/0.6.0/binary-linux-22 \ 14 | && chmod a+x binary-linux-22 15 | 16 | RUN mkdir matchstick 17 | WORKDIR /matchstick 18 | 19 | # Commenting out for now as it seems there's no need to copy when using bind mount 20 | # COPY ./ . 21 | 22 | CMD ../binary-linux-22 ${ARGS} 23 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/tests/priceChainlink.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { 3 | assert, 4 | describe, 5 | test, 6 | } from "matchstick-as/assembly/index"; 7 | 8 | import { ERC20_LUSD } from "../src/contracts/Constants"; 9 | import { getPrice } from "../src/price/PriceLookup"; 10 | import { mockPriceFeed } from "./chainlink"; 11 | 12 | describe("price chainlink", () => { 13 | test("resolves chainlink price", () => { 14 | const lusdPrice = BigDecimal.fromString("1.01"); 15 | mockPriceFeed(ERC20_LUSD, lusdPrice); 16 | 17 | const price = getPrice(ERC20_LUSD, BigInt.fromString("1")); 18 | assert.stringEquals(price.toString(), lusdPrice.toString()); 19 | }); 20 | }); -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/walletHelper.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction } from "matchstick-as"; 3 | 4 | export const mockWalletBalance = ( 5 | tokenAddress: string, 6 | walletAddress: string, 7 | value: BigInt, 8 | ): void => { 9 | createMockedFunction( 10 | Address.fromString(tokenAddress), 11 | "balanceOf", 12 | "balanceOf(address):(uint256)", 13 | ) 14 | .withArgs([ethereum.Value.fromAddress(Address.fromString(walletAddress))]) 15 | .returns([ethereum.Value.fromUnsignedBigInt(value)]); 16 | }; 17 | 18 | export const mockZeroWalletBalances = (tokenAddress: string, walletAddresses: string[]): void => { 19 | for (let i = 0; i < walletAddresses.length; i++) { 20 | mockWalletBalance(tokenAddress, walletAddresses[i], BigInt.fromString("0")); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /subgraphs/fantom/src/treasury/Assets.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { 4 | TokenCategoryStable, 5 | TokenCategoryVolatile, 6 | } from "../../../shared/src/contracts/TokenDefinition"; 7 | import { getOwnedLiquidityBalances } from "./OwnedLiquidity"; 8 | import { getTokenBalances } from "./TokenBalances"; 9 | 10 | export function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): void { 11 | getTokenBalances(timestamp, TokenCategoryStable, blockNumber); 12 | 13 | getTokenBalances(timestamp, TokenCategoryVolatile, blockNumber); 14 | 15 | getOwnedLiquidityBalances(timestamp, blockNumber); 16 | } 17 | 18 | export function handleAssets(block: ethereum.Block): void { 19 | log.debug("handleAssets: *** Indexing block {}", [block.number.toString()]); 20 | generateTokenRecords(block.timestamp, block.number); 21 | } 22 | -------------------------------------------------------------------------------- /subgraphs/polygon/src/treasury/Assets.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { 4 | TokenCategoryStable, 5 | TokenCategoryVolatile, 6 | } from "../../../shared/src/contracts/TokenDefinition"; 7 | import { getOwnedLiquidityBalances } from "./OwnedLiquidity"; 8 | import { getTokenBalances } from "./TokenBalances"; 9 | 10 | export function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): void { 11 | getTokenBalances(timestamp, TokenCategoryStable, blockNumber); 12 | 13 | getTokenBalances(timestamp, TokenCategoryVolatile, blockNumber); 14 | 15 | getOwnedLiquidityBalances(timestamp, blockNumber); 16 | } 17 | 18 | export function handleAssets(block: ethereum.Block): void { 19 | log.debug("handleAssets: *** Indexing block {}", [block.number.toString()]); 20 | generateTokenRecords(block.timestamp, block.number); 21 | } 22 | -------------------------------------------------------------------------------- /subgraphs/berachain/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Berachain Subgraph Changelog 2 | 3 | ## 1.3.6 (2025-09-15) 4 | 5 | - Amend price lookup route for IBERA for greater accuracy 6 | 7 | ## 1.3.2 (2025-04-03) 8 | 9 | - Adds support for BEX OHM-HONEY Reward Vault 10 | - Adds support for lBGT 11 | 12 | ## 1.2.8 (2025-03-25) 13 | 14 | - Adds support for Infrared Kodiak OHM-HONEY Reward Vault 15 | 16 | ## 1.1.4 (2025-02-20) 17 | 18 | - Adds support for Beradrome Kodiak OHM-HONEY Reward Vault v2 19 | 20 | ## 1.1.3 (2025-02-18) 21 | 22 | - Adds support for indexing native BERA 23 | - Fixes issue with double-counting of OHM-HONEY LP balances 24 | 25 | ## 1.0.4 (2025-02-14) 26 | 27 | - Fix issue with Beradrome Kodiak OHM-HONEY LP and Kodiak OHM-HONEY LP balances conflicting 28 | 29 | ## 1.0.3 (2025-02-13) 30 | 31 | - Initial subgraph version 32 | - Supports tokens: OHM, IBERA, USDC.e, and HONEY 33 | - Supports the OHM-HONEY LP 34 | -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/LendingMarketDeployment.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BigDecimal, 3 | BigInt 4 | } from "@graphprotocol/graph-ts"; 5 | 6 | export class LendingMarketDeployment { 7 | protected token: string; 8 | protected blockNumber: BigInt; 9 | protected amount: BigDecimal; 10 | protected address: string; 11 | 12 | // Constructor 13 | constructor(token: string, blockNumber: BigInt, amount: BigDecimal, address: string) { 14 | this.token = token; 15 | this.blockNumber = blockNumber; 16 | this.amount = amount; 17 | this.address = address; 18 | } 19 | 20 | // Getter methods 21 | getToken(): string { 22 | return this.token; 23 | } 24 | 25 | getBlockNumber(): BigInt { 26 | return this.blockNumber; 27 | } 28 | 29 | getAmount(): BigDecimal { 30 | return this.amount; 31 | } 32 | 33 | getAddress(): string { 34 | return this.address; 35 | } 36 | } -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/DateHelper.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | /** 4 | * Returns in YYYY-MM-DD format 5 | * 6 | * @param date 7 | * @returns 8 | */ 9 | export const getISO8601DateString = (date: Date): string => { 10 | return date.toISOString().split("T")[0]; 11 | }; 12 | 13 | export const getISO8601DateStringFromTimestamp = (timestamp: BigInt): string => { 14 | const date = new Date(timestamp.toI64() * 1000); 15 | return getISO8601DateString(date); 16 | }; 17 | 18 | export const getISO8601StringFromTimestamp = (timestamp: BigInt): string => { 19 | const date = new Date(timestamp.toI64() * 1000); 20 | return date.toISOString(); 21 | }; 22 | 23 | export const getDateFromBlockTimestamp = (timestamp: BigInt): Date => { 24 | return new Date(timestamp.toI64() * 1000); 25 | } 26 | 27 | export const addDays = (date: Date, days: u64): Date => { 28 | const newDate = new Date(date.getTime() + days * (24 * 60 * 60 * 1000)); 29 | 30 | return newDate; 31 | }; 32 | -------------------------------------------------------------------------------- /subgraphs/shared/tests/ERC20Helper.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction } from "matchstick-as"; 3 | 4 | import { ERC20 } from "../generated/Price/ERC20"; 5 | import { toBigInt } from "../src/utils/Decimals"; 6 | 7 | export function mockERC20Balance(walletAddress: string, tokenAddress: string, balance: BigDecimal): void { 8 | const contract = ERC20.bind(Address.fromString(tokenAddress)); 9 | 10 | createMockedFunction(Address.fromString(tokenAddress), "balanceOf", "balanceOf(address):(uint256)"). 11 | withArgs([ethereum.Value.fromAddress(Address.fromString(walletAddress))]). 12 | returns([ 13 | ethereum.Value.fromUnsignedBigInt(toBigInt(balance, contract.decimals())) 14 | ]); 15 | } 16 | 17 | export function mockERC20Balances(walletAddresses: string[], tokenAddress: string, balance: BigDecimal): void { 18 | for (let i = 0; i < walletAddresses.length; i++) { 19 | mockERC20Balance(walletAddresses[i], tokenAddress, balance); 20 | } 21 | } -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/PairHandler.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-shadow 2 | export const enum PairHandlerTypes { 3 | UniswapV2, 4 | UniswapV3, 5 | Curve, 6 | Balancer, 7 | FraxSwap, 8 | ERC4626, 9 | } 10 | 11 | /** 12 | * Represents a mapping between a liquidity pool's handler 13 | * (e.g. UniswapV2Pair) and the contract address. 14 | * 15 | * @module PairHandler 16 | */ 17 | export class PairHandler { 18 | protected type: PairHandlerTypes; 19 | protected contract: string; 20 | protected pool: string | null; 21 | 22 | /** 23 | * Creates a new mapping. 24 | * 25 | * @constructor 26 | * @param type The type of the liquidity pair 27 | * @param contract The liquidity pair/vault address 28 | */ 29 | constructor(type: PairHandlerTypes, contract: string, pool: string | null = null) { 30 | this.type = type; 31 | this.contract = contract; 32 | this.pool = pool; 33 | } 34 | 35 | getType(): PairHandlerTypes { 36 | return this.type; 37 | } 38 | 39 | getContract(): string { 40 | return this.contract; 41 | } 42 | 43 | getPool(): string | null { 44 | return this.pool; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bin/subgraph/src/networkHandler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement this interface to handle queries related to a specific network. 3 | */ 4 | export interface NetworkHandler { 5 | network: string; 6 | outputPath: string; 7 | subgraphId: string; 8 | branch: string; 9 | 10 | doLatestDate(): void; 11 | doQuery(): void; 12 | doComparison(): void; 13 | } 14 | 15 | export class BaseNetworkHandler implements NetworkHandler { 16 | network: string; 17 | subgraphId: string; 18 | branch: string; 19 | outputPath: string; 20 | 21 | constructor(network: string, outputPath: string, subgraphId?: string, branch?: string) { 22 | console.info(`Created network handler with network ${network}, outputPath ${outputPath}, subgraphId ${subgraphId}, branch ${branch}`); 23 | 24 | this.network = network; 25 | this.subgraphId = subgraphId; 26 | this.branch = branch; 27 | this.outputPath = outputPath; 28 | } 29 | 30 | public async doLatestDate(): Promise { 31 | throw new Error("Method not implemented."); 32 | } 33 | public async doQuery(): Promise { 34 | throw new Error("Method not implemented."); 35 | } 36 | public async doComparison(): Promise { 37 | throw new Error("Method not implemented."); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/ArrayHelper.ts: -------------------------------------------------------------------------------- 1 | import { TokenRecord, TokenSupply } from "../../generated/schema"; 2 | 3 | /** 4 | * Determines if the given string array loosely includes the given value. 5 | * 6 | * This is used as Array.includes() uses strict equality, and the strings 7 | * provided by {Address} are not always the same. 8 | * 9 | * This also ensures that when comparison is performed, both strings 10 | * are lowercase. 11 | * 12 | * @param array the array to iterate over 13 | * @param value the value to check against 14 | * @returns 15 | */ 16 | export function arrayIncludesLoose(array: string[], value: string): boolean { 17 | for (let i = 0; i < array.length; i++) { 18 | if (array[i].toLowerCase() == value.toLowerCase()) return true; 19 | } 20 | 21 | return false; 22 | } 23 | 24 | export function pushTokenRecordArray(destinationArray: TokenRecord[], sourceArray: TokenRecord[]): void { 25 | for (let i = 0; i < sourceArray.length; i++) { 26 | destinationArray.push(sourceArray[i]); 27 | } 28 | } 29 | 30 | export function pushTokenSupplyArray(destinationArray: TokenSupply[], sourceArray: TokenSupply[]): void { 31 | for (let i = 0; i < sourceArray.length; i++) { 32 | destinationArray.push(sourceArray[i]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Arbitrum Subgraph Changelog 2 | 3 | ## 1.7.9 (2024-07-17) 4 | 5 | - Adds POL in Camelot 6 | - Change the Chainlink price feed used as a trigger in order to increase indexing speed 7 | - Reduce code paths for determining protocol addresses 8 | - Modify Uniswap V2 PriceHandler to return 0 if the contract reverts 9 | 10 | ## 1.6.4 (2023-09-13) 11 | 12 | - Apply write-off of JonesDAO token, since it cannot be sold. 13 | - Going forward, dynamically determine the OHM supply minted into Silo Finance and Sentiment Finance 14 | 15 | ## 1.5.3 (2023-07-18) 16 | 17 | - Adds support for the Lending AMO 18 | - Adds manual deployments of OHM to Silo and Sentiment 19 | - Index OHM in treasury wallets 20 | - Amended the event trigger to reduce frequency 21 | 22 | ## 1.4.3 (2023-05-05) 23 | 24 | - Add wETH-OHM and OHM-USDC POL 25 | 26 | ## 1.3.4 (2023-05-03) 27 | 28 | - Add LUSD, LQTY on Arbitrum 29 | - Add OHM on Arbitrum 30 | - Add Chainlink price feeds for LUSD, USDC, WETH 31 | - Add OHM total supply 32 | 33 | ## 1.2.2 (2023-04-29) 34 | 35 | - Populate TokenSupply records with the quantity of protocol-owned gOHM 36 | 37 | ## 1.1.4 (2023-04-27) 38 | 39 | - Adds ARB token 40 | - Shift to event handler as a trigger for indexing, which should be faster than a block handler 41 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/tokenStablecoins.test.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from "@graphprotocol/graph-ts"; 2 | import { assert, beforeEach, clearStore, describe, log,test } from "matchstick-as/assembly/index"; 3 | 4 | import { NATIVE_ETH } from "../src/utils/Constants"; 5 | import { getStablecoinBalance } from "../src/utils/TokenStablecoins"; 6 | import { mockClearinghouseRegistryAddressNull, mockTreasuryAddressNull } from "./bophadesHelper"; 7 | import { mockEthUsdRate } from "./pairHelper"; 8 | 9 | const TIMESTAMP = BigInt.fromString("1"); 10 | 11 | beforeEach(() => { 12 | log.debug("beforeEach: Clearing store", []); 13 | clearStore(); 14 | 15 | // Do at the start, as it can be used by mock functions 16 | mockTreasuryAddressNull(); 17 | mockClearinghouseRegistryAddressNull(); 18 | }); 19 | 20 | describe("native ETH", () => { 21 | test("getStablecoinBalance does not throw error", () => { 22 | mockEthUsdRate(); 23 | 24 | const records = getStablecoinBalance( 25 | TIMESTAMP, 26 | NATIVE_ETH, 27 | false, 28 | BigInt.fromString("14000000"), 29 | ); 30 | 31 | // Native ETH isn't an ERC20 contract, and isn't supported by TheGraph API. 32 | // The code shouldn't throw any errors/exceptions when it comes across native ETH. 33 | assert.i32Equals(0, records.length); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/bophadesHelper.ts: -------------------------------------------------------------------------------- 1 | import { Address, Bytes, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction, log } from "matchstick-as"; 3 | 4 | export function mockTreasuryAddressNull(): void { 5 | // Mock the Bophades Kernel contract to revert and hence return null 6 | const kernelAddress = Address.fromString("0x2286d7f9639e8158FaD1169e76d1FbC38247f54b"); 7 | 8 | createMockedFunction( 9 | kernelAddress, 10 | "getModuleForKeycode", 11 | "getModuleForKeycode(bytes5):(address)" 12 | ).withArgs( 13 | [ 14 | ethereum.Value.fromFixedBytes(Bytes.fromUTF8("TRSRY")), 15 | ], 16 | ).reverts(); 17 | log.debug("mockTreasuryAddressNull: mocked null TRSRY address", []); 18 | } 19 | 20 | export function mockClearinghouseRegistryAddressNull(): void { 21 | // Mock the Bophades Kernel contract to revert and hence return null 22 | const kernelAddress = Address.fromString("0x2286d7f9639e8158FaD1169e76d1FbC38247f54b"); 23 | 24 | createMockedFunction( 25 | kernelAddress, 26 | "getModuleForKeycode", 27 | "getModuleForKeycode(bytes5):(address)" 28 | ).withArgs( 29 | [ 30 | ethereum.Value.fromFixedBytes(Bytes.fromUTF8("CHREG")), 31 | ], 32 | ).reverts(); 33 | log.debug("mockClearinghouseRegistryAddressNull: mocked null CHREG address", []); 34 | } 35 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/GOhmCalculations.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { getOrCreateERC20TokenSnapshot } from "../contracts/ERC20"; 4 | import { ERC20_GOHM } from "./Constants"; 5 | 6 | /** 7 | * Returns the total supply of the gOHM token at the given block number. 8 | * 9 | * If the ERC20 contract cannot be loaded, 0 will be returned. 10 | * 11 | * @param blockNumber the current block number 12 | * @returns BigDecimal presenting the total supply at {blockNumber} 13 | */ 14 | export function getGOhmTotalSupply(blockNumber: BigInt): BigDecimal { 15 | const snapshot = getOrCreateERC20TokenSnapshot(ERC20_GOHM, blockNumber); 16 | const snapshotTotalSupply = snapshot.totalSupply; 17 | 18 | if (snapshotTotalSupply === null) { 19 | log.error( 20 | "getTotalSupply: Expected to be able to bind to OHM contract at address {} for block {}, but it was not found.", 21 | [ERC20_GOHM, blockNumber.toString()], 22 | ); 23 | return BigDecimal.zero(); 24 | } 25 | 26 | return snapshotTotalSupply; 27 | } 28 | 29 | /** 30 | * gOHM circulating supply is synthetically calculated as: 31 | * 32 | * OHM floating supply / current index 33 | */ 34 | export function getGOhmSyntheticSupply(ohmFloatingSupply: BigDecimal, currentIndex: BigDecimal): BigDecimal { 35 | return ohmFloatingSupply.div(currentIndex); 36 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/tests/chainlink.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction, log } from "matchstick-as"; 3 | 4 | import { toBigInt } from "../../shared/src/utils/Decimals"; 5 | import { ERC20_LUSD, ERC20_USDC, ERC20_WETH } from "../src/contracts/Constants"; 6 | import { getPriceFeed } from "../src/price/PriceChainlink"; 7 | 8 | export function mockPriceFeed(token: string, price: BigDecimal): void { 9 | const priceFeed = getPriceFeed(token); 10 | if (priceFeed == null) { 11 | throw new Error(`No price feed for token ${token}`); 12 | } 13 | 14 | const PRICE_FEED_DECIMALS = 8; 15 | const priceFeedAddress = Address.fromString(priceFeed!); 16 | 17 | createMockedFunction(priceFeedAddress, "decimals", "decimals():(uint8)").returns([ 18 | ethereum.Value.fromI32(PRICE_FEED_DECIMALS), 19 | ]); 20 | 21 | log.debug("Mocking price feed value {} for token {}", [price.toString(), token]); 22 | createMockedFunction(priceFeedAddress, "latestAnswer", "latestAnswer():(int256)").returns([ 23 | ethereum.Value.fromSignedBigInt(toBigInt(price, PRICE_FEED_DECIMALS)), 24 | ]); 25 | } 26 | 27 | export function mockStablecoinsPriceFeeds(): void { 28 | const tokens = [ERC20_LUSD, ERC20_USDC, ERC20_WETH]; 29 | for (let i = 0; i < tokens.length; i++) { 30 | mockPriceFeed(tokens[i], BigDecimal.fromString("1")); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/TokensForChain.ts: -------------------------------------------------------------------------------- 1 | import { ERC20_TOKENS_ARBITRUM } from "../../../arbitrum/src/contracts/Constants"; 2 | import { ERC20_TOKENS_BASE } from "../../../base/src/contracts/Constants"; 3 | import { ERC20_TOKENS_BERACHAIN } from "../../../berachain/src/contracts/Constants"; 4 | import { ERC20_TOKENS } from "../../../ethereum/src/utils/Constants"; 5 | import { ERC20_TOKENS_FANTOM } from "../../../fantom/src/contracts/Constants"; 6 | import { ERC20_TOKENS_POLYGON } from "../../../polygon/src/contracts/Constants"; 7 | import { TokenDefinition } from "../contracts/TokenDefinition"; 8 | 9 | /** 10 | /** 11 | * Returns the ERC20 tokens for a given blockchain. 12 | * @param blockchain - The blockchain to get the tokens for. 13 | * @returns A map of token addresses to token definitions. 14 | */ 15 | export function getTokensForChain(blockchain: string): Map { 16 | if (blockchain === "Ethereum") { 17 | return ERC20_TOKENS; 18 | } 19 | if (blockchain === "Polygon") { 20 | return ERC20_TOKENS_POLYGON; 21 | } 22 | if (blockchain === "Base") { 23 | return ERC20_TOKENS_BASE; 24 | } 25 | if (blockchain === "Arbitrum") { 26 | return ERC20_TOKENS_ARBITRUM; 27 | } 28 | if (blockchain === "Berachain") { 29 | return ERC20_TOKENS_BERACHAIN; 30 | } 31 | if (blockchain === "Fantom") { 32 | return ERC20_TOKENS_FANTOM; 33 | } 34 | return new Map(); 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Build files 64 | **/**/tests/.bin/ 65 | **/**/tests/.latest.json 66 | **/**/build/ 67 | 68 | # query-test 69 | **/**/records-base.json 70 | **/**/records-branch.json 71 | **/**/comparison.json 72 | 73 | # Generated from a template 74 | matchstick.yaml 75 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/tests/priceLookup.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { 3 | assert, 4 | describe, 5 | test, 6 | } from "matchstick-as/assembly/index"; 7 | 8 | import { ERC20_USDC } from "../src/contracts/Constants"; 9 | import { getPrice } from "../src/price/PriceLookup"; 10 | import { mockPriceFeed } from "./chainlink"; 11 | 12 | describe("base token", () => { 13 | test("resolves USDC price", () => { 14 | const usdcPrice = BigDecimal.fromString("1.01"); 15 | mockPriceFeed(ERC20_USDC, usdcPrice); 16 | 17 | const price = getPrice(ERC20_USDC, BigInt.fromString("1")); 18 | assert.stringEquals(price.toString(), usdcPrice.toString()); 19 | }); 20 | 21 | test("resolves USDC price - case-insensitive parameter to getPrice", () => { 22 | const usdcPrice = BigDecimal.fromString("1.01"); 23 | mockPriceFeed(ERC20_USDC, usdcPrice); 24 | 25 | const price = getPrice("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", BigInt.fromString("1")); 26 | assert.stringEquals(price.toString(), usdcPrice.toString()); 27 | }); 28 | 29 | test("resolves USDC price - case-insensitive price feed", () => { 30 | const usdcPrice = BigDecimal.fromString("1.01"); 31 | mockPriceFeed("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", usdcPrice); 32 | 33 | const price = getPrice(ERC20_USDC, BigInt.fromString("1")); 34 | assert.stringEquals(price.toString(), usdcPrice.toString()); 35 | }); 36 | }); -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/Decimals.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | export const DEFAULT_DECIMALS = 18; 4 | 5 | export function pow(base: BigDecimal, exponent: number): BigDecimal { 6 | let result = base; 7 | 8 | if (exponent == 0) { 9 | return BigDecimal.fromString("1"); 10 | } 11 | 12 | for (let i = 2; i <= exponent; i++) { 13 | result = result.times(base); 14 | } 15 | 16 | return result; 17 | } 18 | 19 | /** 20 | * Converts the given BigInt to a BigDecimal. 21 | * 22 | * If the `decimals` parameter is specified, that will be used instead of `DEFAULT_DECIMALS`. 23 | * 24 | * @param value 25 | * @param decimals 26 | * @returns 27 | */ 28 | export function toDecimal(value: BigInt, decimals: number = DEFAULT_DECIMALS): BigDecimal { 29 | const precision = BigInt.fromI32(10) 30 | .pow(decimals) 31 | .toBigDecimal(); 32 | 33 | return value.divDecimal(precision); 34 | } 35 | 36 | /** 37 | * Converts the given BigDecimal to a BigInt. 38 | * 39 | * If the `decimals` parameter is specified, that will be used instead of `DEFAULT_DECIMALS`. 40 | * 41 | * @param value 42 | * @param decimals 43 | * @returns 44 | */ 45 | export function toBigInt(value: BigDecimal, decimals: number = DEFAULT_DECIMALS): BigInt { 46 | const multiplier = BigInt.fromI32(10) 47 | .pow(decimals) 48 | .toBigDecimal(); 49 | 50 | return BigInt.fromString(value.times(multiplier).toString()); 51 | } 52 | -------------------------------------------------------------------------------- /subgraphs/price-snapshot/src/helpers/Math.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | export function getDelta(a: BigDecimal, b: BigDecimal | null): BigDecimal | null { 4 | if (!b || b.equals(BigDecimal.zero())) { 5 | return null; 6 | } 7 | 8 | return a.div(b).minus(BigDecimal.fromString("1")); 9 | } 10 | 11 | export function getMean(values: BigDecimal[]): BigDecimal { 12 | let total = BigDecimal.zero(); 13 | 14 | for (let i = 0; i < values.length; i++) { 15 | total = total.plus(values[i]); 16 | } 17 | 18 | return total.div(BigDecimal.fromString(values.length.toString())); 19 | } 20 | 21 | export function getStandardDeviation(values: BigDecimal[], count: u32): BigDecimal | null { 22 | if (values.length != count) { 23 | return null; 24 | } 25 | 26 | // https://www.mathsisfun.com/data/standard-deviation-formulas.html 27 | const mean = getMean(values); 28 | 29 | const meanDifferences: BigDecimal[] = []; 30 | for (let i = 0; i < values.length; i++) { 31 | const currentValue = values[i]; 32 | const meanDifference = currentValue.minus(mean); 33 | const meanDifferenceSq = meanDifference.times(meanDifference); 34 | 35 | meanDifferences.push(meanDifferenceSq); 36 | } 37 | 38 | const meanDifferencesMean = getMean(meanDifferences); 39 | 40 | return BigDecimal.fromString(sqrt(parseFloat(meanDifferencesMean.toString())).toString()); 41 | } 42 | -------------------------------------------------------------------------------- /bin/subgraph/src/helpers/subgraphConfig.ts: -------------------------------------------------------------------------------- 1 | import { InvalidArgumentError } from "commander"; 2 | import { existsSync, readFileSync } from "fs"; 3 | 4 | export interface SubgraphConfig { 5 | id: string; 6 | version: string; 7 | org: string; 8 | name: string; 9 | } 10 | 11 | /** 12 | * Reads the per-network configuration file at {path}. 13 | * 14 | * @param path 15 | * @returns 16 | * @throws InvalidArgumentError if the file cannot be found 17 | */ 18 | export const readConfig = (path: string): SubgraphConfig => { 19 | if (!existsSync(path)) { 20 | throw new InvalidArgumentError( 21 | `The network configuration file ${path} does not exist. Please create it.`, 22 | ); 23 | } 24 | 25 | return JSON.parse(readFileSync(path, "utf8")) as SubgraphConfig; 26 | }; 27 | 28 | /** 29 | * Asserts that the required properties are present. 30 | * 31 | * @param config 32 | * @throws InvalidArgumentError if a required property is not found 33 | */ 34 | export const assertConfig = (config: SubgraphConfig): void => { 35 | // if (!config.id) { 36 | // throw new InvalidArgumentError("id must be set to the subgraph id ('Qm...')"); 37 | // } 38 | 39 | if (!config.version) { 40 | throw new InvalidArgumentError("version must be set"); 41 | } 42 | 43 | if (!config.org) { 44 | throw new InvalidArgumentError("org must be set"); 45 | } 46 | 47 | if (!config.name) { 48 | throw new InvalidArgumentError("name must be set"); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/erc20Helper.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction } from "matchstick-as"; 3 | 4 | export const ERC20_STANDARD_DECIMALS = 18; 5 | 6 | export const mockERC20TotalSupply = ( 7 | token: string, 8 | tokenDecimals: i32, 9 | totalSupply: BigInt, 10 | ): void => { 11 | const tokenAddress = Address.fromString(token); 12 | 13 | // Total supply 14 | createMockedFunction(tokenAddress, "totalSupply", "totalSupply():(uint256)").returns([ 15 | ethereum.Value.fromUnsignedBigInt(totalSupply), 16 | ]); 17 | 18 | // Token decimals 19 | createMockedFunction(tokenAddress, "decimals", "decimals():(uint8)").returns([ 20 | ethereum.Value.fromI32(tokenDecimals), 21 | ]); 22 | }; 23 | 24 | export const mockERC20Decimals = ( 25 | token: string, 26 | decimals: i32, 27 | ): void => { 28 | createMockedFunction(Address.fromString(token), "decimals", "decimals():(uint8)").returns([ 29 | ethereum.Value.fromI32(decimals), 30 | ]); 31 | }; 32 | 33 | export const mockERC20Balance = ( 34 | token: string, 35 | wallet: string, 36 | balance: BigInt, 37 | ): void => { 38 | createMockedFunction(Address.fromString(token), "balanceOf", "balanceOf(address):(uint256)"). 39 | withArgs([ethereum.Value.fromAddress(Address.fromString(wallet))]). 40 | returns([ 41 | ethereum.Value.fromUnsignedBigInt(balance), 42 | ]); 43 | } -------------------------------------------------------------------------------- /subgraphs/shared/src/contracts/sOlympus.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { sOlympusERC20 } from "../../generated/Price/sOlympusERC20"; 4 | import { sOlympusERC20V2 } from "../../generated/Price/sOlympusERC20V2"; 5 | import { sOlympusERC20V3 } from "../../generated/Price/sOlympusERC20V3"; 6 | 7 | export function getSOlympusERC20( 8 | contractAddress: string, 9 | currentBlockNumber: BigInt, 10 | ): sOlympusERC20 { 11 | log.debug("Binding sOlympusERC20 contract for address {}. Block number {}", [ 12 | contractAddress, 13 | currentBlockNumber.toString(), 14 | ]); 15 | return sOlympusERC20.bind(Address.fromString(contractAddress)); 16 | } 17 | 18 | export function getSOlympusERC20V2( 19 | contractAddress: string, 20 | currentBlockNumber: BigInt, 21 | ): sOlympusERC20V2 { 22 | log.debug("Binding sOlympusERC20V2 contract for address {}. Block number {}", [ 23 | contractAddress, 24 | currentBlockNumber.toString(), 25 | ]); 26 | return sOlympusERC20V2.bind(Address.fromString(contractAddress)); 27 | } 28 | 29 | export function getSOlympusERC20V3( 30 | contractAddress: string, 31 | currentBlockNumber: BigInt, 32 | ): sOlympusERC20V3 { 33 | log.debug("Binding sOlympusERC20V3 contract for address {}. Block number {}", [ 34 | contractAddress, 35 | currentBlockNumber.toString(), 36 | ]); 37 | return sOlympusERC20V3.bind(Address.fromString(contractAddress)); 38 | } -------------------------------------------------------------------------------- /subgraphs/shared/tests/price/PriceHandlerCustomMapping.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { assert, describe, test } from "matchstick-as/assembly/index"; 3 | 4 | import { ContractNameLookup } from "../../src/contracts/ContractLookup"; 5 | import { PriceLookup, PriceLookupResult } from "../../src/price/PriceHandler"; 6 | import { PriceHandlerCustomMapping } from "../../src/price/PriceHandlerCustomMapping"; 7 | 8 | const ERC20_KLIMA = "0x4e78011ce80ee02d2c3e649fb657e45898257815".toLowerCase(); 9 | const ERC20_KLIMA_STAKED = "0xb0C22d8D350C67420f06F48936654f567C73E8C8".toLowerCase(); 10 | const BLOCK = BigInt.fromString("1"); 11 | 12 | describe("getPrice", () => { 13 | test("returns mapped token value", () => { 14 | const priceLookup: PriceLookup = (_tokenAddress: string, _block: BigInt): PriceLookupResult => { 15 | return { 16 | liquidity: BigDecimal.fromString("0"), 17 | price: BigDecimal.fromString("1.5"), 18 | }; 19 | }; 20 | 21 | const contractLookup: ContractNameLookup = (_tokenAddress: string): string => "sKLIMA"; 22 | 23 | const handler = new PriceHandlerCustomMapping( 24 | ERC20_KLIMA, 25 | [ERC20_KLIMA_STAKED], 26 | contractLookup, 27 | ); 28 | 29 | // Should return the price of KLIMA 30 | const priceResult = handler.getPrice(ERC20_KLIMA_STAKED, priceLookup, BLOCK); 31 | assert.stringEquals("1.5", priceResult ? priceResult.price.toString() : ""); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /subgraphs/ethereum/abis/OlympusStakingV2Helper.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_staking", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_OHM", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [], 20 | "name": "OHM", 21 | "outputs": [ 22 | { 23 | "internalType": "address", 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "stateMutability": "view", 29 | "type": "function" 30 | }, 31 | { 32 | "inputs": [ 33 | { 34 | "internalType": "uint256", 35 | "name": "_amount", 36 | "type": "uint256" 37 | } 38 | ], 39 | "name": "stake", 40 | "outputs": [], 41 | "stateMutability": "nonpayable", 42 | "type": "function" 43 | }, 44 | { 45 | "inputs": [], 46 | "name": "staking", 47 | "outputs": [ 48 | { 49 | "internalType": "address", 50 | "name": "", 51 | "type": "address" 52 | } 53 | ], 54 | "stateMutability": "view", 55 | "type": "function" 56 | } 57 | ] -------------------------------------------------------------------------------- /subgraphs/shared/src/contracts/TokenDefinition.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | export const TokenCategoryStable = "Stable"; 4 | export const TokenCategoryVolatile = "Volatile"; 5 | export const TokenCategoryPOL = "Protocol-Owned Liquidity"; 6 | 7 | export class TokenDefinition { 8 | protected address: string; 9 | protected category: string; 10 | protected isLiquid: boolean; 11 | protected isVolatileBluechip: boolean; 12 | protected liquidBackingMultiplier: BigDecimal | null; 13 | 14 | /** 15 | * Creates a new token definition. 16 | * 17 | * @constructor 18 | * @param address 19 | * @param category 20 | * @param isLiquid 21 | * @param isVolatileBluechip 22 | */ 23 | constructor(address: string, category: string, isLiquid: boolean, isVolatileBluechip: boolean, liquidBackingMultiplier: BigDecimal | null = null) { 24 | this.address = address.toLowerCase(); 25 | this.category = category; 26 | this.isLiquid = isLiquid; 27 | this.isVolatileBluechip = isVolatileBluechip; 28 | this.liquidBackingMultiplier = liquidBackingMultiplier; 29 | } 30 | 31 | getAddress(): string { 32 | return this.address; 33 | } 34 | 35 | getCategory(): string { 36 | return this.category; 37 | } 38 | 39 | getIsLiquid(): boolean { 40 | return this.isLiquid; 41 | } 42 | 43 | getIsVolatileBluechip(): boolean { 44 | return this.isVolatileBluechip; 45 | } 46 | 47 | getLiquidBackingMultiplier(): BigDecimal | null { 48 | return this.liquidBackingMultiplier; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/decimals.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { assert, describe, test } from "matchstick-as/assembly/index"; 3 | 4 | import { toBigInt, toDecimal } from "../../shared/src/utils/Decimals"; 5 | 6 | describe("toDecimal", () => { 7 | test("applies default decimal places", () => { 8 | const integer = BigInt.fromString("10000000000000000000"); 9 | 10 | assert.stringEquals(toDecimal(integer).toString(), "10"); 11 | }); 12 | 13 | test("applies custom decimal places", () => { 14 | const integer = BigInt.fromString("10000000000000000000"); 15 | 16 | assert.stringEquals(toDecimal(integer, 17).toString(), "100"); 17 | }); 18 | 19 | test("handles decimal values", () => { 20 | const integer = BigInt.fromString("10123000000000000000"); 21 | 22 | assert.stringEquals(toDecimal(integer).toString(), "10.123"); 23 | }); 24 | }); 25 | 26 | describe("toBigInt", () => { 27 | test("applies default decimal places", () => { 28 | const decimal = BigDecimal.fromString("10"); 29 | 30 | assert.stringEquals(toBigInt(decimal).toString(), "10000000000000000000"); 31 | }); 32 | 33 | test("applies custom decimal places", () => { 34 | const decimal = BigDecimal.fromString("10"); 35 | 36 | assert.stringEquals(toBigInt(decimal, 4).toString(), "100000"); 37 | }); 38 | 39 | test("handles decimal values", () => { 40 | const decimal = BigDecimal.fromString("10.1234"); 41 | 42 | assert.stringEquals(toBigInt(decimal).toString(), "10123400000000000000"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /subgraphs/shared/src/supply/OhmCalculations.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20_SOHM_V2, ERC20_SOHM_V3, ERC20_SOHM_V3_BLOCK } from "../Constants"; 4 | import { getSOlympusERC20V2, getSOlympusERC20V3 } from "../contracts/sOlympus"; 5 | import { toDecimal } from "../utils/Decimals"; 6 | 7 | /** 8 | * Returns the current staking index, based on the current block. 9 | * 10 | * If the current block is after {ERC20_SOHM_V3_BLOCK}, the index from the 11 | * sOHM v3 contract is returned. 12 | * 13 | * Otherwise, the index from the sOHM v2 contract is returned. 14 | * 15 | * @param blockNumber the current block number 16 | * @returns BigDecimal 17 | */ 18 | export function getCurrentIndex(blockNumber: BigInt): BigDecimal { 19 | if (blockNumber.gt(BigInt.fromString(ERC20_SOHM_V3_BLOCK))) { 20 | const contractV3 = getSOlympusERC20V3(ERC20_SOHM_V3, blockNumber); 21 | 22 | return toDecimal(contractV3.index(), 9); 23 | } else { 24 | /** 25 | * {ERC20_SOHM_V2_BLOCK} is far before the typical starting block of this subgraph (14,000,000), 26 | * so we don't really need to test for it. 27 | * 28 | * TODO: However, if we do ever push the starting block back before {ERC20_SOHM_V2_BLOCK}, we need 29 | * to consider how to determine the index from sOHM V1, as it doesn't have an `index()` function. 30 | */ 31 | const contractV2 = getSOlympusERC20V2(ERC20_SOHM_V2, blockNumber); 32 | 33 | return toDecimal(contractV2.index(), 9); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /subgraphs/base/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.8 2 | description: Olympus Protocol Metrics Subgraph - Base 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | features: 5 | - grafting 6 | graft: 7 | base: QmYgw9fWpiriYBj6NL8U8952Gsrxfvdfe3hxHE25UJm56A # 1.0.2 8 | block: 25311259 # POL updates 9 | schema: 10 | file: ../../schema.graphql 11 | dataSources: 12 | - kind: ethereum/contract 13 | name: TokenRecords-base 14 | network: base 15 | source: 16 | address: "0x060cb087a9730E13aa191f31A6d86bFF8DfcdCC0" # OHM 17 | abi: ERC20 18 | startBlock: 13204827 # 2024-04-15 19 | mapping: 20 | kind: ethereum/events 21 | apiVersion: 0.0.6 22 | language: wasm/assemblyscript 23 | entities: 24 | - TokenRecord 25 | - TokenSupply 26 | abis: 27 | - name: ERC20 28 | file: ../shared/abis/ERC20.json 29 | # Price Lookup 30 | - name: UniswapV2Pair 31 | file: ../shared/abis/UniswapV2Pair.json 32 | - name: UniswapV3Pair 33 | file: ../shared/abis/UniswapV3Pair.json 34 | - name: UniswapV3PositionManager 35 | file: ../shared/abis/UniswapV3PositionManager.json 36 | - name: ChainlinkPriceFeed 37 | file: ../shared/abis/ChainlinkPriceFeed.json 38 | blockHandlers: 39 | - handler: handleBlock 40 | filter: 41 | kind: polling 42 | # Every 8 hours 43 | # 0.5 blocks every second * 60 seconds * 60 minutes * 8 hours 44 | every: 7200 45 | file: ./src/treasury/Assets.ts 46 | -------------------------------------------------------------------------------- /subgraphs/fantom/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.8 2 | description: Olympus Protocol Metrics Subgraph - Fantom 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | # features: 5 | # - grafting 6 | # graft: 7 | # base: QmWTwjzoLhNUugdJmszcMeA38eEuTkpTeDdhHnjMjdLwrD # 0.0.7 8 | # block: 58674875 # 2023-03-30 9 | schema: 10 | file: ../../schema.graphql 11 | dataSources: 12 | - kind: ethereum/contract 13 | name: TokenRecords-fantom 14 | network: fantom 15 | source: 16 | address: "0x91fa20244Fb509e8289CA630E5db3E9166233FDc" 17 | abi: gOHM 18 | startBlock: 37320000 # 2022-05-01 19 | mapping: 20 | kind: ethereum/events 21 | apiVersion: 0.0.6 22 | language: wasm/assemblyscript 23 | entities: 24 | - TokenRecord 25 | - TokenSupply 26 | abis: 27 | # Basic 28 | - name: ERC20 29 | file: ../shared/abis/ERC20.json 30 | - name: gOHM 31 | file: ../shared/abis/gOHM.json 32 | # Price Lookup 33 | - name: BalancerVault 34 | file: ../shared/abis/BalancerVault.json 35 | - name: BalancerPoolToken 36 | file: ../shared/abis/BalancerPoolToken.json 37 | - name: UniswapV2Pair 38 | file: ../shared/abis/UniswapV2Pair.json 39 | - name: UniswapV3Pair 40 | file: ../shared/abis/UniswapV3Pair.json 41 | blockHandlers: 42 | - handler: handleAssets 43 | filter: 44 | kind: polling 45 | # Only index every 24000 blocks, approximately 8 hours 46 | every: 24000 47 | file: ./src/treasury/Assets.ts 48 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | build: 16 | strategy: 17 | matrix: 18 | # We want to test the shared code too 19 | subgraph: [ethereum, arbitrum, polygon, fantom, shared, base, berachain] 20 | # The type of runner that the job will run on 21 | # Pin the runner to ubuntu-22.04 to avoid an incompatibility with ubuntu 24 and graph test 22 | # https://github.com/graphprotocol/graph-tooling/issues/1546 23 | runs-on: ubuntu-22.04 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 28 | - uses: actions/checkout@v3 29 | 30 | - name: Setup Node 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: "20" 34 | cache: "yarn" 35 | 36 | # Install dependencies 37 | - name: Install 38 | run: | 39 | yarn install --frozen-lockfile 40 | 41 | # Run build 42 | - name: Build 43 | run: yarn subgraph build ${{ matrix.subgraph }} 44 | 45 | # Run tests 46 | - name: Unit Tests 47 | run: yarn subgraph test ${{ matrix.subgraph }} 48 | -------------------------------------------------------------------------------- /subgraphs/base/src/price/PriceChainlink.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 4 | import { ChainlinkPriceFeed } from "../../generated/TokenRecords-base/ChainlinkPriceFeed"; 5 | import { ERC20_USDC,ERC20_WETH } from "../contracts/Constants"; 6 | 7 | const tokenPriceFeedMap: Map = new Map(); 8 | tokenPriceFeedMap.set(ERC20_WETH, "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70".toLowerCase()); 9 | tokenPriceFeedMap.set(ERC20_USDC, "0x7e860098F58bBFC8648a4311b374B1D669a2bc6B".toLowerCase()); 10 | 11 | export function getPriceFeedTokens(): string[] { 12 | return tokenPriceFeedMap.keys(); 13 | } 14 | 15 | export function getPriceFeed(token: string): string | null { 16 | const tokenLower = token.toLowerCase(); 17 | if (!tokenPriceFeedMap.has(tokenLower)) { 18 | return null; 19 | } 20 | 21 | return tokenPriceFeedMap.get(tokenLower); 22 | } 23 | 24 | export function getPriceFeedValue(tokenAddress: string): BigDecimal | null { 25 | const tokenAddressLower = tokenAddress.toLowerCase(); 26 | if (!tokenPriceFeedMap.has(tokenAddressLower)) { 27 | return null; 28 | } 29 | 30 | const priceFeedAddress = tokenPriceFeedMap.get(tokenAddressLower); 31 | const priceFeed = ChainlinkPriceFeed.bind(Address.fromString(priceFeedAddress)); 32 | const decimalsResult = priceFeed.try_decimals(); 33 | const answerResult = priceFeed.try_latestAnswer(); 34 | 35 | if (decimalsResult.reverted || answerResult.reverted) { 36 | return null; 37 | } 38 | 39 | return toDecimal(answerResult.value, decimalsResult.value); 40 | } 41 | -------------------------------------------------------------------------------- /subgraphs/price-snapshot/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.4 2 | description: Olympus Price Snapshot 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | schema: 5 | file: ./schema.graphql 6 | dataSources: 7 | - kind: ethereum/contract 8 | name: PriceSnapshot 9 | network: mainnet 10 | source: 11 | address: "0x8116B273cD75d79C382aFacc706659DEd5E0a59d" # Aave Chainlink Price Feed, which updates every hour 12 | abi: ChainlinkPriceFeed 13 | startBlock: 16842061 14 | mapping: 15 | kind: ethereum/events 16 | apiVersion: 0.0.6 17 | language: wasm/assemblyscript 18 | entities: 19 | - PriceSnapshot 20 | abis: 21 | - name: ERC20 22 | file: ../shared/abis/ERC20.json 23 | # Used for price resolution 24 | - name: UniswapV2Pair 25 | file: ../shared/abis/UniswapV2Pair.json 26 | - name: UniswapV3Pair 27 | file: ../shared/abis/UniswapV3Pair.json 28 | - name: BalancerVault 29 | file: ../shared/abis/BalancerVault.json 30 | - name: BalancerPoolToken 31 | file: ../shared/abis/BalancerPoolToken.json 32 | # Used for base tokens 33 | - name: ChainlinkPriceFeed 34 | file: ../shared/abis/ChainlinkPriceFeed.json 35 | # Used to get current index 36 | - name: sOlympusERC20V2 37 | file: ../shared/abis/sOlympusERC20V2.json 38 | - name: sOlympusERC20V3 39 | file: ../shared/abis/sOlympusERC20V3.json 40 | eventHandlers: 41 | - event: NewRound(indexed uint256,indexed address,uint256) 42 | handler: handleEvent 43 | file: ./src/PriceSnapshot.ts 44 | -------------------------------------------------------------------------------- /subgraphs/shared/tests/price/PriceHandlerCustom.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { PriceHandler, PriceLookup, PriceLookupResult } from "../../src/price/PriceHandler"; 4 | 5 | /** 6 | * Price handler used in tests that will return a specific value passed into the constructor. 7 | */ 8 | export class PriceHandlerCustom implements PriceHandler { 9 | protected returnValue: PriceLookupResult; 10 | 11 | constructor(returnValue: PriceLookupResult) { 12 | this.returnValue = returnValue; 13 | } 14 | 15 | getTokens(): string[] { 16 | return []; 17 | } 18 | 19 | exists(): boolean { 20 | return true; 21 | } 22 | 23 | getId(): string { 24 | return "PriceHandlerCustom"; 25 | } 26 | 27 | matches(_tokenAddress: string): boolean { 28 | return true; 29 | } 30 | 31 | getPrice( 32 | _tokenAddress: string, 33 | _priceLookup: PriceLookup, 34 | _block: BigInt, 35 | ): PriceLookupResult | null { 36 | return this.returnValue; 37 | } 38 | 39 | getTotalValue( 40 | _excludedTokens: string[], 41 | _priceLookup: PriceLookup, 42 | _block: BigInt, 43 | ): BigDecimal | null { 44 | throw new Error("Method not implemented."); 45 | } 46 | 47 | getUnitPrice(_priceLookup: PriceLookup, _block: BigInt): BigDecimal | null { 48 | throw new Error("Method not implemented."); 49 | } 50 | 51 | getBalance(_walletAddress: string, _block: BigInt): BigDecimal { 52 | throw new Error("Method not implemented."); 53 | } 54 | 55 | getUnderlyingTokenBalance(_walletAddress: string, _tokenAddress: string, _block: BigInt): BigDecimal { 56 | throw new Error("Method not implemented."); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // Inspired by: https://github.com/CosmWasm/cosmwasm/pull/118/files 2 | { 3 | "env": { 4 | "es6": true 5 | }, 6 | "globals": { 7 | // testing 8 | "describe": false, 9 | "it": false, 10 | "expect": false, 11 | // assemblyscript runtime 12 | "u64": false, 13 | "i64": false, 14 | "unreachable": false, 15 | "unmanaged": false, 16 | "idof": false, 17 | "changetype": false, 18 | "memory": false, 19 | "load": false, 20 | "store": false, 21 | "__alloc": false, 22 | "__release": false, 23 | "__retain": false 24 | }, 25 | "parser": "@typescript-eslint/parser", 26 | "parserOptions": { 27 | "ecmaVersion": 2018 28 | }, 29 | "plugins": ["@typescript-eslint", "simple-import-sort"], 30 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 31 | "rules": { 32 | "curly": ["warn", "multi-line", "consistent"], 33 | "no-console": ["warn", { "allow": ["error", "info", "warn"] }], 34 | "no-param-reassign": "warn", 35 | "no-shadow": "warn", 36 | "prefer-const": "warn", 37 | "spaced-comment": ["warn", "always", { "line": { "markers": ["/ = new Map(); 8 | tokenPriceFeedMap.set(ERC20_HONEY, "0x4BAD96DD1C7D541270a0C92e1D4e5f12EEEA7a57".toLowerCase()); // Redstone USDC (since 1:1 with USDC) 9 | tokenPriceFeedMap.set(ERC20_STARGATE_USDC, "0x4BAD96DD1C7D541270a0C92e1D4e5f12EEEA7a57".toLowerCase()); // Redstone USDC 10 | 11 | export function getPriceFeedTokens(): string[] { 12 | return tokenPriceFeedMap.keys(); 13 | } 14 | 15 | export function getPriceFeed(token: string): string | null { 16 | const tokenLower = token.toLowerCase(); 17 | if (!tokenPriceFeedMap.has(tokenLower)) { 18 | return null; 19 | } 20 | 21 | return tokenPriceFeedMap.get(tokenLower); 22 | } 23 | 24 | export function getPriceFeedValue(tokenAddress: string): BigDecimal | null { 25 | const tokenAddressLower = tokenAddress.toLowerCase(); 26 | if (!tokenPriceFeedMap.has(tokenAddressLower)) { 27 | return null; 28 | } 29 | 30 | const priceFeedAddress = tokenPriceFeedMap.get(tokenAddressLower); 31 | const priceFeed = ChainlinkPriceFeed.bind(Address.fromString(priceFeedAddress)); 32 | const decimalsResult = priceFeed.try_decimals(); 33 | const answerResult = priceFeed.try_latestAnswer(); 34 | 35 | if (decimalsResult.reverted || answerResult.reverted) { 36 | return null; 37 | } 38 | 39 | return toDecimal(answerResult.value, decimalsResult.value); 40 | } 41 | -------------------------------------------------------------------------------- /subgraphs/polygon/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.8 2 | description: Olympus Protocol Metrics Subgraph - Polygon 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | # features: 5 | # - grafting 6 | # graft: 7 | # base: Qmayo4ydvieNxGzLcHdpNZZZCgeqBqcxtL4sTiEjdbDwKo 8 | # block: 40300000 # 2023-03-13 9 | schema: 10 | file: ../../schema.graphql 11 | dataSources: 12 | - kind: ethereum/contract 13 | name: TokenRecords-polygon 14 | network: matic 15 | source: 16 | # We would ideally use rebase() on the KlimaStaking contract, but Polygon's nodes do not support trace, so we can't use a call handler 17 | address: "0xd8cA34fd379d9ca3C6Ee3b3905678320F5b45195" # gOHM Polygon 18 | abi: gOHM 19 | startBlock: 27790000 # 2022-05-01 20 | mapping: 21 | kind: ethereum/events 22 | apiVersion: 0.0.6 23 | language: wasm/assemblyscript 24 | entities: 25 | - TokenRecord 26 | - TokenSupply 27 | abis: 28 | - name: ERC20 29 | file: ../shared/abis/ERC20.json 30 | - name: gOHM 31 | file: ../shared/abis/gOHM.json 32 | # Price Lookup 33 | - name: BalancerVault 34 | file: ../shared/abis/BalancerVault.json 35 | - name: BalancerPoolToken 36 | file: ../shared/abis/BalancerPoolToken.json 37 | - name: UniswapV2Pair 38 | file: ../shared/abis/UniswapV2Pair.json 39 | - name: UniswapV3Pair 40 | file: ../shared/abis/UniswapV3Pair.json 41 | blockHandlers: 42 | - handler: handleAssets 43 | filter: 44 | kind: polling 45 | # Only index every 14,400th block, approximately 8 hours 46 | every: 14400 47 | file: ./src/treasury/Assets.ts 48 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/price/PriceChainlink.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 4 | import { ChainlinkPriceFeed } from "../../generated/TokenRecords-arbitrum/ChainlinkPriceFeed"; 5 | import { ERC20_LUSD, ERC20_USDC, ERC20_WETH } from "../contracts/Constants"; 6 | 7 | const tokenPriceFeedMap: Map = new Map(); 8 | tokenPriceFeedMap.set(ERC20_LUSD, "0x0411d28c94d85a36bc72cb0f875dfa8371d8ffff".toLowerCase()); 9 | tokenPriceFeedMap.set(ERC20_USDC, "0x50834f3163758fcc1df9973b6e91f0f0f0434ad3".toLowerCase()); 10 | tokenPriceFeedMap.set(ERC20_WETH, "0x639fe6ab55c921f74e7fac1ee960c0b6293ba612".toLowerCase()); 11 | 12 | export function getPriceFeedTokens(): string[] { 13 | return tokenPriceFeedMap.keys(); 14 | } 15 | 16 | export function getPriceFeed(token: string): string | null { 17 | const tokenLower = token.toLowerCase(); 18 | if (!tokenPriceFeedMap.has(tokenLower)) { 19 | return null; 20 | } 21 | 22 | return tokenPriceFeedMap.get(tokenLower); 23 | } 24 | 25 | export function getPriceFeedValue(tokenAddress: string): BigDecimal | null { 26 | const tokenAddressLower = tokenAddress.toLowerCase(); 27 | if (!tokenPriceFeedMap.has(tokenAddressLower)) { 28 | return null; 29 | } 30 | 31 | const priceFeedAddress = tokenPriceFeedMap.get(tokenAddressLower); 32 | const priceFeed = ChainlinkPriceFeed.bind(Address.fromString(priceFeedAddress)); 33 | const decimalsResult = priceFeed.try_decimals(); 34 | const answerResult = priceFeed.try_latestAnswer(); 35 | 36 | if (decimalsResult.reverted || answerResult.reverted) { 37 | return null; 38 | } 39 | 40 | return toDecimal(answerResult.value, decimalsResult.value); 41 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/price/PriceBase.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { getContractName } from "../contracts/Contracts"; 4 | import { getPriceFeedValue } from "./PriceChainlink"; 5 | 6 | export function isBaseToken(baseToken: string): boolean { 7 | const FUNC = "isBaseToken"; 8 | const priceFeedValue = getPriceFeedValue(baseToken); 9 | 10 | if (priceFeedValue !== null) { 11 | log.debug("{}: Token {} is a base token", [FUNC, getContractName(baseToken)]); 12 | return true; 13 | } 14 | 15 | log.debug("{}: Token {} is not a base token", [FUNC, getContractName(baseToken)]); 16 | return false; 17 | } 18 | 19 | /** 20 | * Gets the USD value of the base token. 21 | * 22 | * This enables pairs to have ETH or DAI/USDC/USDT as the base token. 23 | * 24 | * @param baseToken 25 | * @param blockNumber 26 | * @returns 27 | */ 28 | export function getBaseTokenRate( 29 | baseToken: Address, 30 | blockNumber: BigInt, 31 | ): BigDecimal { 32 | const FUNC = "getBaseTokenRate"; 33 | const baseTokenAddress = baseToken.toHexString().toLowerCase(); 34 | if (!isBaseToken(baseTokenAddress)) { 35 | throw new Error( 36 | `${FUNC}: Token ${getContractName( 37 | baseTokenAddress, 38 | )} is unsupported for base token price lookup`, 39 | ); 40 | } 41 | 42 | const usdRate = getPriceFeedValue(baseTokenAddress); 43 | if (usdRate === null || usdRate.equals(BigDecimal.zero())) { 44 | throw new Error(`${FUNC}: Unable to determine USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) at block ${blockNumber.toString()}`); 45 | } 46 | 47 | log.debug(`${FUNC}: USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) is ${usdRate.toString()}`, []); 48 | return usdRate; 49 | } 50 | -------------------------------------------------------------------------------- /subgraphs/base/src/price/PriceBase.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { getContractName } from "../contracts/Contracts"; 4 | import { getPriceFeedValue } from "./PriceChainlink"; 5 | 6 | export function isBaseToken(baseToken: string): boolean { 7 | const FUNC = "isBaseToken"; 8 | const priceFeedValue = getPriceFeedValue(baseToken); 9 | 10 | if (priceFeedValue !== null) { 11 | log.debug("{}: Token {} is a base token", [FUNC, getContractName(baseToken)]); 12 | return true; 13 | } 14 | 15 | log.debug("{}: Token {} is not a base token", [FUNC, getContractName(baseToken)]); 16 | return false; 17 | } 18 | 19 | /** 20 | * Gets the USD value of the base token. 21 | * 22 | * This enables pairs to have ETH or DAI/USDC/USDT as the base token. 23 | * 24 | * @param baseToken 25 | * @param blockNumber 26 | * @returns 27 | */ 28 | export function getBaseTokenRate( 29 | baseToken: Address, 30 | blockNumber: BigInt, 31 | ): BigDecimal { 32 | const FUNC = "getBaseTokenRate"; 33 | const baseTokenAddress = baseToken.toHexString().toLowerCase(); 34 | if (!isBaseToken(baseTokenAddress)) { 35 | throw new Error( 36 | `${FUNC}: Token ${getContractName( 37 | baseTokenAddress, 38 | )} is unsupported for base token price lookup`, 39 | ); 40 | } 41 | 42 | const usdRate = getPriceFeedValue(baseTokenAddress); 43 | if (usdRate === null || usdRate.equals(BigDecimal.zero())) { 44 | throw new Error(`${FUNC}: Unable to determine USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) at block ${blockNumber.toString()}`); 45 | } 46 | 47 | log.debug(`${FUNC}: USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) is ${usdRate.toString()}`, []); 48 | return usdRate; 49 | } 50 | -------------------------------------------------------------------------------- /subgraphs/shared/subgraph.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: this is a dummy manifest and is not to be used for deployment 3 | # See README.md for more details 4 | # 5 | specVersion: 0.0.4 6 | description: Olympus Protocol Metrics Subgraph - Shared 7 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 8 | schema: 9 | file: ../../schema.graphql 10 | dataSources: 11 | - kind: ethereum/contract 12 | name: Price 13 | network: ethereum 14 | source: 15 | address: "0x8D9bA570D6cb60C7e3e0F31343Efe75AB8E65FB1" # gOHM Arbitrum 16 | abi: gOHM 17 | startBlock: 10950000 # 2022-05-01 18 | mapping: 19 | kind: ethereum/events 20 | apiVersion: 0.0.6 21 | language: wasm/assemblyscript 22 | entities: 23 | - TokenRecord 24 | abis: 25 | - name: ERC20 26 | file: ./abis/ERC20.json 27 | - name: ERC4626 28 | file: ./abis/ERC4626.json 29 | - name: gOHM 30 | file: ./abis/gOHM.json 31 | # OHM Calculations 32 | - name: sOlympusERC20 33 | file: ../shared/abis/sOlympusERC20.json 34 | - name: sOlympusERC20V2 35 | file: ../shared/abis/sOlympusERC20V2.json 36 | - name: sOlympusERC20V3 37 | file: ../shared/abis/sOlympusERC20V3.json 38 | # Price Lookup 39 | - name: BalancerVault 40 | file: ./abis/BalancerVault.json 41 | - name: BalancerPoolToken 42 | file: ./abis/BalancerPoolToken.json 43 | - name: UniswapV2Pair 44 | file: ./abis/UniswapV2Pair.json 45 | - name: UniswapV3Pair 46 | file: ./abis/UniswapV3Pair.json 47 | - name: UniswapV3PositionManager 48 | file: ./abis/UniswapV3PositionManager.json 49 | - name: UniswapV3Quoter 50 | file: ./abis/UniswapV3Quoter.json 51 | blockHandlers: 52 | - handler: handleAssets 53 | file: ./src/Dummy.ts 54 | 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "olympus-subgraph", 3 | "version": "0.1.0", 4 | "description": "Subgraphs for OlympusDAO. NOTE: This does not build a package.", 5 | "author": { 6 | "name": "Jem", 7 | "email": "0x0xjem@gmail.com", 8 | "url": "https://github.com/0xJem" 9 | }, 10 | "private": true, 11 | "repository": "https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph", 12 | "scripts": { 13 | "create-local": "graph create --node http://localhost:8020/ olympus", 14 | "remove-local": "graph remove --node http://localhost:8020/ olympus", 15 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 olympus", 16 | "ls-deadcode": "yarn ts-prune -p tsconfig.ts-prune.json | grep -v 'used in module'", 17 | "subgraph": "yarn ts-node bin/subgraph/src/index.ts", 18 | "lint": "eslint --fix ." 19 | }, 20 | "dependencies": { 21 | "@apollo/client": "^3.8.4", 22 | "@graphprotocol/graph-cli": "^0.96.0", 23 | "@graphprotocol/graph-ts": "^0.35.0", 24 | "assemblyscript-json": "^1.1.0", 25 | "commander": "^9.4.0", 26 | "cross-fetch": "^4.0.0", 27 | "dotenv": "^16.0.2", 28 | "graphql": "^16.8.1", 29 | "node-fetch": "^2.6.7", 30 | "ts-node": "^10.9.1" 31 | }, 32 | "devDependencies": { 33 | "@types/json-diff": "^0.7.0", 34 | "@types/node": "^20", 35 | "@typescript-eslint/eslint-plugin": "^5.44.0", 36 | "@typescript-eslint/parser": "^5.44.0", 37 | "eslint": "^8.23.0", 38 | "eslint-plugin-simple-import-sort": "^8.0.0", 39 | "json-diff": "^0.9.0", 40 | "matchstick-as": "^0.5.0", 41 | "mustache": "^4.2.0", 42 | "prettier": "^2.6.2", 43 | "ts-prune": "^0.10.3", 44 | "typescript": "^4.9.3" 45 | }, 46 | "resolutions": { 47 | "ejs": "^3.1.7", 48 | "node-forge": "^1.3.0", 49 | "node-fetch": "^2.6.7", 50 | "ramda": "^0.27.2", 51 | "yargs-parser": "^18.1.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /subgraphs/berachain/src/price/PriceBase.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { getContractName } from "../contracts/Contracts"; 4 | import { getPriceFeedValue } from "./PriceChainlink"; 5 | 6 | export function isBaseToken(baseToken: string): boolean { 7 | const FUNC = "isBaseToken"; 8 | const priceFeedValue = getPriceFeedValue(baseToken); 9 | 10 | if (priceFeedValue !== null) { 11 | log.debug("{}: Token {} is a base token", [FUNC, getContractName(baseToken)]); 12 | return true; 13 | } 14 | 15 | log.debug("{}: Token {} is not a base token", [FUNC, getContractName(baseToken)]); 16 | return false; 17 | } 18 | 19 | /** 20 | * Gets the USD value of the base token. 21 | * 22 | * This enables pairs to have ETH or DAI/USDC/USDT as the base token. 23 | * 24 | * @param baseToken 25 | * @param blockNumber 26 | * @returns 27 | */ 28 | export function getBaseTokenRate( 29 | baseToken: Address, 30 | blockNumber: BigInt, 31 | ): BigDecimal { 32 | const FUNC = "getBaseTokenRate"; 33 | const baseTokenAddress = baseToken.toHexString().toLowerCase(); 34 | if (!isBaseToken(baseTokenAddress)) { 35 | throw new Error( 36 | `${FUNC}: Token ${getContractName( 37 | baseTokenAddress, 38 | )} is unsupported for base token price lookup`, 39 | ); 40 | } 41 | 42 | log.info("{} Getting USD rate for token {} ({})", [FUNC, getContractName(baseTokenAddress), baseTokenAddress]); 43 | const usdRate = getPriceFeedValue(baseTokenAddress); 44 | if (usdRate === null || usdRate.equals(BigDecimal.zero())) { 45 | throw new Error(`${FUNC}: Unable to determine USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) at block ${blockNumber.toString()}`); 46 | } 47 | 48 | log.debug(`${FUNC}: USD rate for token ${getContractName(baseTokenAddress)} (${baseTokenAddress}) is ${usdRate.toString()}`, []); 49 | return usdRate; 50 | } 51 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/GnosisAuction.ts: -------------------------------------------------------------------------------- 1 | import { log } from "@graphprotocol/graph-ts"; 2 | 3 | import { toDecimal } from "../../shared/src/utils/Decimals"; 4 | import { GnosisAuctionLaunched } from "../generated/BondManager/BondManager"; 5 | import { AuctionCleared } from "../generated/ProtocolMetrics/GnosisEasyAuction"; 6 | import { GnosisAuction, GnosisAuctionRoot } from "../generated/schema"; 7 | 8 | export const GNOSIS_RECORD_ID = "GnosisAuction"; 9 | 10 | export function handleGnosisAuctionLaunched(event: GnosisAuctionLaunched): void { 11 | // We have one one record in GnosisAuction 12 | let rootRecord: GnosisAuctionRoot | null = GnosisAuctionRoot.load(GNOSIS_RECORD_ID); 13 | if (!rootRecord) { 14 | rootRecord = new GnosisAuctionRoot(GNOSIS_RECORD_ID); 15 | rootRecord.markets = []; 16 | } 17 | 18 | log.debug("Adding Gnosis Auction with id {} to GnosisAuction record", [event.params.marketId.toString()]); 19 | const markets = rootRecord.markets; 20 | markets.push(event.params.marketId); 21 | rootRecord.markets = markets; 22 | 23 | rootRecord.save(); 24 | 25 | const auctionRecord = new GnosisAuction(event.params.marketId.toString()); 26 | auctionRecord.auctionOpenTimestamp = event.block.timestamp; 27 | auctionRecord.payoutCapacity = toDecimal(event.params.capacity, 9); 28 | auctionRecord.termSeconds = event.params.bondTerm; 29 | auctionRecord.save(); 30 | } 31 | 32 | export function handleGnosisAuctionCleared(event: AuctionCleared): void { 33 | // If we don't have a record of it, ignore (as it is probably for another token) 34 | const record: GnosisAuction | null = GnosisAuction.load(event.params.auctionId.toString()); 35 | if (!record) { 36 | return; 37 | } 38 | 39 | // Save the number of OHM tokens sold 40 | record.bidQuantity = toDecimal(event.params.soldBiddingTokens, 9); 41 | record.auctionCloseTimestamp = event.block.timestamp; 42 | record.save(); 43 | } 44 | -------------------------------------------------------------------------------- /subgraphs/price-snapshot/schema.graphql: -------------------------------------------------------------------------------- 1 | type PriceSnapshot @entity(immutable: true) { 2 | id: ID! # Block number 3 | block: BigInt! 4 | timestamp: BigInt! 5 | date: String! 6 | ohmUsdPrice: BigDecimal! 7 | ohmUsdPrice1dDelta: BigDecimal 8 | ohmUsdPrice30dVolatility: BigDecimal 9 | gOhmUsdPrice: BigDecimal! 10 | } 11 | 12 | # Used to access the latest PriceSnapshot for a particular date 13 | type PriceSnapshotDaily @entity { 14 | id: ID! # YYYY-MM-DD 15 | record: String! # ID of latest PriceSnapshot 16 | } 17 | 18 | ### Added from protocol-metrics subgraph. Used for pricing. 19 | 20 | type ERC20TokenSnapshot @entity(immutable: true) { 21 | id: Bytes! # address/block 22 | address: Bytes! 23 | decimals: Int! 24 | totalSupply: BigDecimal 25 | } 26 | 27 | type ConvexRewardPoolSnapshot @entity(immutable: true) { 28 | id: ID! # lowercase address/block 29 | block: BigInt! 30 | address: Bytes! 31 | stakingToken: Bytes! 32 | } 33 | 34 | # TODO migrate to PoolSnapshot 35 | type BalancerPoolSnapshot @entity(immutable: true) { 36 | id: Bytes! # pool id/block 37 | block: BigInt! 38 | pool: Bytes! 39 | poolToken: Bytes! 40 | decimals: Int! 41 | totalSupply: BigDecimal! 42 | tokens: [Bytes!]! 43 | balances: [BigDecimal!]! 44 | weights: [BigDecimal!]! 45 | } 46 | 47 | type PoolSnapshot @entity(immutable: true) { 48 | id: Bytes! # pool/block 49 | block: BigInt! 50 | pool: Bytes! 51 | poolToken: Bytes 52 | decimals: Int! 53 | totalSupply: BigDecimal! 54 | tokens: [Bytes!]! 55 | balances: [BigDecimal!]! 56 | weights: [BigDecimal!] 57 | } 58 | 59 | type TokenPriceSnapshot @entity(immutable: true) { 60 | id: Bytes! # address/block 61 | block: BigInt! 62 | token: Bytes! 63 | price: BigDecimal! 64 | } 65 | 66 | type StakingPoolSnapshot @entity(immutable: true) { 67 | id: Bytes! # address/block 68 | block: BigInt! 69 | contractAddress: Bytes! 70 | stakingToken: Bytes # Will not be set if the call reverts 71 | } 72 | -------------------------------------------------------------------------------- /subgraphs/base/src/treasury/Assets.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord, TokenSupply } from "../../../shared/generated/schema"; 4 | import { 5 | TokenCategoryStable, 6 | TokenCategoryVolatile, 7 | } from "../../../shared/src/contracts/TokenDefinition"; 8 | import { pushTokenRecordArray, pushTokenSupplyArray } from "../../../shared/src/utils/ArrayHelper"; 9 | import { getProtocolOwnedLiquiditySupplyRecords, getTotalSupply, getTreasuryOHMRecords } from "./OhmCalculations"; 10 | import { getOwnedLiquidityBalances } from "./OwnedLiquidity"; 11 | import { getTokenBalances } from "./TokenBalances"; 12 | 13 | function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 14 | const records: TokenRecord[] = []; 15 | 16 | pushTokenRecordArray( 17 | records, 18 | getTokenBalances(timestamp, TokenCategoryStable, blockNumber), 19 | ); 20 | 21 | pushTokenRecordArray( 22 | records, 23 | getTokenBalances(timestamp, TokenCategoryVolatile, blockNumber) 24 | ); 25 | 26 | pushTokenRecordArray( 27 | records, 28 | getOwnedLiquidityBalances(timestamp, blockNumber) 29 | ); 30 | 31 | return records; 32 | } 33 | 34 | function generateTokenSupplies(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 35 | const records: TokenSupply[] = []; 36 | 37 | // Total supply 38 | pushTokenSupplyArray( 39 | records, 40 | getTotalSupply(timestamp, blockNumber), 41 | ); 42 | 43 | // Treasury OHM 44 | pushTokenSupplyArray( 45 | records, 46 | getTreasuryOHMRecords(timestamp, blockNumber), 47 | ); 48 | 49 | // POL 50 | pushTokenSupplyArray( 51 | records, 52 | getProtocolOwnedLiquiditySupplyRecords(timestamp, blockNumber), 53 | ); 54 | 55 | return records; 56 | } 57 | 58 | export function handleBlock(block: ethereum.Block): void { 59 | log.debug("handleBlock: *** Indexing block {}", [block.number.toString()]); 60 | generateTokenRecords(block.timestamp, block.number); 61 | generateTokenSupplies(block.timestamp, block.number); 62 | } 63 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/contracts/ERC20.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt, Bytes, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 4 | import { ERC20 } from "../../generated/ProtocolMetrics/ERC20"; 5 | import { ERC20TokenSnapshot } from "../../generated/schema"; 6 | import { getContractName } from "../utils/Constants"; 7 | 8 | export function getOrCreateERC20TokenSnapshot(address: string, blockNumber: BigInt): ERC20TokenSnapshot { 9 | const FUNC = "getOrCreateERC20TokenSnapshot"; 10 | const snapshotId = Bytes.fromHexString(address).concatI32(blockNumber.toI32()); 11 | let token = ERC20TokenSnapshot.load(snapshotId); 12 | if (token == null) { 13 | token = new ERC20TokenSnapshot(snapshotId); 14 | token.address = Address.fromString(address); 15 | 16 | const erc20Contract = ERC20.bind(Address.fromString(address)); 17 | // decimals() will revert if the contract has not yet been deployed 18 | const decimalsResult = erc20Contract.try_decimals(); 19 | // Only set the values if there has been a deployment 20 | if (!decimalsResult.reverted) { 21 | token.decimals = erc20Contract.decimals(); 22 | } 23 | else { 24 | log.debug("{}: call to decimals() on ERC20 contract {} ({}) reverted. Setting decimals to 0.", [FUNC, getContractName(address), address]); 25 | token.decimals = 0; 26 | } 27 | 28 | // Only calculate totalSupply if the contract call is successful, and there is a valid decimals number 29 | const totalSupplyResult = erc20Contract.try_totalSupply(); 30 | if (!decimalsResult.reverted && !totalSupplyResult.reverted) { 31 | token.totalSupply = toDecimal(erc20Contract.totalSupply(), token.decimals); 32 | } 33 | 34 | token.save(); 35 | } 36 | 37 | return token; 38 | } 39 | 40 | export function getERC20Decimals(address: string, blockNumber: BigInt): number { 41 | return getOrCreateERC20TokenSnapshot(address, blockNumber).decimals; 42 | } 43 | -------------------------------------------------------------------------------- /subgraphs/berachain/src/treasury/Assets.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord, TokenSupply } from "../../../shared/generated/schema"; 4 | import { 5 | TokenCategoryStable, 6 | TokenCategoryVolatile, 7 | } from "../../../shared/src/contracts/TokenDefinition"; 8 | import { pushTokenRecordArray, pushTokenSupplyArray } from "../../../shared/src/utils/ArrayHelper"; 9 | import { getProtocolOwnedLiquiditySupplyRecords, getTotalSupply, getTreasuryOHMRecords } from "./OhmCalculations"; 10 | import { getOwnedLiquidityBalances } from "./OwnedLiquidity"; 11 | import { getTokenBalances } from "./TokenBalances"; 12 | 13 | function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 14 | const records: TokenRecord[] = []; 15 | 16 | pushTokenRecordArray( 17 | records, 18 | getTokenBalances(timestamp, TokenCategoryStable, blockNumber), 19 | ); 20 | 21 | pushTokenRecordArray( 22 | records, 23 | getTokenBalances(timestamp, TokenCategoryVolatile, blockNumber) 24 | ); 25 | 26 | pushTokenRecordArray( 27 | records, 28 | getOwnedLiquidityBalances(timestamp, blockNumber) 29 | ); 30 | 31 | return records; 32 | } 33 | 34 | function generateTokenSupplies(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 35 | const records: TokenSupply[] = []; 36 | 37 | // Total supply 38 | pushTokenSupplyArray( 39 | records, 40 | getTotalSupply(timestamp, blockNumber), 41 | ); 42 | 43 | // Treasury OHM 44 | pushTokenSupplyArray( 45 | records, 46 | getTreasuryOHMRecords(timestamp, blockNumber), 47 | ); 48 | 49 | // POL 50 | pushTokenSupplyArray( 51 | records, 52 | getProtocolOwnedLiquiditySupplyRecords(timestamp, blockNumber), 53 | ); 54 | 55 | return records; 56 | } 57 | 58 | export function handleBlock(block: ethereum.Block): void { 59 | log.debug("handleBlock: *** Indexing block {}", [block.number.toString()]); 60 | generateTokenRecords(block.timestamp, block.number); 61 | generateTokenSupplies(block.timestamp, block.number); 62 | } 63 | -------------------------------------------------------------------------------- /subgraphs/berachain/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.8 2 | description: Olympus Protocol Metrics Subgraph - Berachain 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | features: 5 | - grafting 6 | graft: 7 | base: QmUboX4f6C2Hb5mZT88NcFg32rXkci6SVAj4qkpgKLHz4V # 1.3.2 8 | block: 2735780 # Infrared vault deployment 9 | schema: 10 | file: ../../schema.graphql 11 | dataSources: 12 | - kind: ethereum/contract 13 | name: TokenRecords-berachain 14 | network: berachain 15 | source: 16 | address: "0x18878Df23e2a36f81e820e4b47b4A40576D3159C" # OHM 17 | abi: ERC20 18 | startBlock: 780014 # Deployment 19 | mapping: 20 | kind: ethereum/events 21 | apiVersion: 0.0.9 22 | language: wasm/assemblyscript 23 | entities: 24 | - TokenRecord 25 | - TokenSupply 26 | abis: 27 | - name: ERC20 28 | file: ../shared/abis/ERC20.json 29 | # Price Lookup 30 | - name: UniswapV3Quoter 31 | file: ../shared/abis/UniswapV3Quoter.json 32 | - name: KodiakIsland 33 | file: ./abis/KodiakIsland.json 34 | - name: BeradromeKodiakIslandRewardVault 35 | file: ./abis/BeradromeKodiakIslandRewardVault.json 36 | - name: UniswapV2Pair 37 | file: ../shared/abis/UniswapV2Pair.json 38 | - name: UniswapV3Pair 39 | file: ../shared/abis/UniswapV3Pair.json 40 | - name: UniswapV3PositionManager 41 | file: ../shared/abis/UniswapV3PositionManager.json 42 | - name: ChainlinkPriceFeed 43 | file: ../shared/abis/ChainlinkPriceFeed.json 44 | - name: BalancerVault 45 | file: ../shared/abis/BalancerVault.json 46 | - name: BalancerPoolToken 47 | file: ../shared/abis/BalancerPoolToken.json 48 | blockHandlers: 49 | - handler: handleBlock 50 | filter: 51 | kind: polling 52 | # Every 4 hours 53 | # 0.5 blocks every second * 60 seconds * 60 minutes * 4 hours 54 | every: 7200 55 | file: ./src/treasury/Assets.ts 56 | -------------------------------------------------------------------------------- /subgraphs/ethereum/tests/chainlink.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, ethereum } from "@graphprotocol/graph-ts"; 2 | import { createMockedFunction, log } from "matchstick-as"; 3 | 4 | import { toBigInt } from "../../shared/src/utils/Decimals"; 5 | import { ERC20_DAI, ERC20_FRAX, ERC20_LUSD, ERC20_USDC, ERC20_WETH } from "../src/utils/Constants"; 6 | import { getPriceFeed } from "../src/utils/PriceChainlink"; 7 | 8 | export function mockPriceFeed(token: string, price: BigDecimal): void { 9 | const priceFeed = getPriceFeed(token); 10 | if (priceFeed == null) { 11 | throw new Error(`No price feed for token ${token}`); 12 | } 13 | 14 | const PRICE_FEED_DECIMALS = 8; 15 | const priceFeedAddress = Address.fromString(priceFeed!); 16 | 17 | createMockedFunction(priceFeedAddress, "decimals", "decimals():(uint8)").returns([ 18 | ethereum.Value.fromI32(PRICE_FEED_DECIMALS), 19 | ]); 20 | 21 | log.debug("Mocking price feed value {} for token {}", [price.toString(), token]); 22 | createMockedFunction(priceFeedAddress, "latestAnswer", "latestAnswer():(int256)").returns([ 23 | ethereum.Value.fromSignedBigInt(toBigInt(price, PRICE_FEED_DECIMALS)), 24 | ]); 25 | } 26 | 27 | export function mockStablecoinsPriceFeeds(): void { 28 | const tokens = [ERC20_DAI, ERC20_FRAX, ERC20_LUSD, ERC20_USDC]; 29 | for (let i = 0; i < tokens.length; i++) { 30 | mockPriceFeed(tokens[i], BigDecimal.fromString("1")); 31 | } 32 | } 33 | 34 | /** 35 | * In March 2023, ETH lookups were changed to use the Chainlink price feed. Tests are setup to use 36 | * a Uniswap pool. Calling this function ensures that the pool is used. 37 | */ 38 | export function mockEthPriceFeedRevert(): void { 39 | const priceFeed = getPriceFeed(ERC20_WETH); 40 | if (priceFeed == null) { 41 | throw new Error(`No price feed for token ${ERC20_WETH}`); 42 | } 43 | 44 | const priceFeedAddress = Address.fromString(priceFeed!); 45 | 46 | createMockedFunction(priceFeedAddress, "decimals", "decimals():(uint8)").reverts(); 47 | 48 | createMockedFunction(priceFeedAddress, "latestAnswer", "latestAnswer():(int256)").reverts(); 49 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/contracts/Sentiment.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenSupply } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { createTokenSupply,TYPE_LENDING } from "../../../shared/src/utils/TokenSupplyHelper"; 6 | import { ERC20 } from "../../generated/TokenRecords-arbitrum/ERC20"; 7 | import { ERC20_OHM, SENTIMENT_LTOKEN } from "./Constants"; 8 | import { getContractName, getWalletAddressesForContract } from "./Contracts"; 9 | 10 | export function getSentimentSupply(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 11 | const records: TokenSupply[] = []; 12 | 13 | const collateralTokenContract = ERC20.bind(Address.fromString(SENTIMENT_LTOKEN)); 14 | const collateralTokenDecimalsResult = collateralTokenContract.try_decimals(); 15 | if (collateralTokenDecimalsResult.reverted) { 16 | return records; 17 | } 18 | const collateralTokenDecimals = collateralTokenDecimalsResult.value; 19 | 20 | // Iterate over wallets to find the balances 21 | const wallets = getWalletAddressesForContract(SENTIMENT_LTOKEN); 22 | for (let i = 0; i < wallets.length; i++) { 23 | const currentWallet = wallets[i]; 24 | 25 | const balanceResult = collateralTokenContract.try_balanceOf(Address.fromString(currentWallet)); 26 | if (balanceResult.reverted) { 27 | continue; 28 | } 29 | 30 | const balance = toDecimal(balanceResult.value, collateralTokenDecimals); 31 | if (balance.equals(BigDecimal.zero())) { 32 | continue; 33 | } 34 | 35 | log.info("getSentimentSupply: Sentiment OHM balance {} for wallet {}", [balance.toString(), getContractName(currentWallet)]); 36 | records.push( 37 | createTokenSupply( 38 | timestamp, 39 | getContractName(ERC20_OHM), 40 | ERC20_OHM, 41 | "Sentiment lOHM", 42 | SENTIMENT_LTOKEN, 43 | getContractName(currentWallet), 44 | currentWallet, 45 | TYPE_LENDING, 46 | balance, 47 | blockNumber, 48 | -1, // Subtract, as this represents OHM taken out of supply 49 | ), 50 | ); 51 | } 52 | 53 | return records; 54 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.8 2 | description: Olympus Protocol Metrics Subgraph - Arbitrum 3 | repository: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph 4 | # Given the block speed of Arbitrum, it is abominably slow to index from scratch. 5 | # Avoid changing the schema, as it will require indexing from scratch. 6 | # features: 7 | # - grafting 8 | # graft: 9 | # base: QmRf3fo7tVsXsj9cQZSgT3Y2R67Hv41T1Ph559oQAmTLe1 # 1.6.4 10 | # block: 190428287 # POL deployment 11 | schema: 12 | file: ../../schema.graphql 13 | dataSources: 14 | - kind: ethereum/contract 15 | name: TokenRecords-arbitrum 16 | network: arbitrum-one 17 | source: 18 | address: "0x5D041081725468Aa43e72ff0445Fde2Ad1aDE775" # FRAX-USD feed, once every 16 hours 19 | abi: ChainlinkAggregator 20 | startBlock: 10950000 # 2022-05-01 21 | mapping: 22 | kind: ethereum/events 23 | apiVersion: 0.0.6 24 | language: wasm/assemblyscript 25 | entities: 26 | - TokenRecord 27 | - TokenSupply 28 | abis: 29 | - name: ERC20 30 | file: ../shared/abis/ERC20.json 31 | - name: gOHM 32 | file: ../shared/abis/gOHM.json 33 | - name: OlympusLender 34 | file: abis/OlympusLender.json 35 | # Price Lookup 36 | - name: BalancerVault 37 | file: ../shared/abis/BalancerVault.json 38 | - name: BalancerPoolToken 39 | file: ../shared/abis/BalancerPoolToken.json 40 | - name: UniswapV2Pair 41 | file: ../shared/abis/UniswapV2Pair.json 42 | - name: UniswapV3Pair 43 | file: ../shared/abis/UniswapV3Pair.json 44 | - name: ChainlinkPriceFeed 45 | file: ../shared/abis/ChainlinkPriceFeed.json 46 | # Custom ABIs 47 | - name: JONESStaking 48 | file: abis/JONESStaking.json 49 | - name: TreasureMining 50 | file: abis/TreasureMining.json 51 | - name: ChainlinkAggregator 52 | file: abis/ChainlinkAggregator.json 53 | eventHandlers: 54 | - event: NewRound(indexed uint256,indexed address,uint256) 55 | handler: handleEvent 56 | file: ./src/treasury/Assets.ts 57 | -------------------------------------------------------------------------------- /subgraphs/shared/src/price/PriceHandlerStablecoin.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ContractNameLookup } from "../contracts/ContractLookup"; 4 | import { arrayIncludesLoose } from "../utils/ArrayHelper"; 5 | import { PriceHandler, PriceLookup, PriceLookupResult } from "./PriceHandler"; 6 | 7 | /** 8 | * Dummy PriceHandler instance to return a value of 1 for the stablecoins specified in the constructor. 9 | */ 10 | export class PriceHandlerStablecoin implements PriceHandler { 11 | protected addresses: string[]; 12 | protected contractLookup: ContractNameLookup; 13 | 14 | constructor(addresses: string[], contractLookup: ContractNameLookup) { 15 | this.addresses = addresses; 16 | this.contractLookup = contractLookup; 17 | } 18 | 19 | getId(): string { 20 | return "PriceHandlerStablecoin"; 21 | } 22 | 23 | exists(): boolean { 24 | return true; 25 | } 26 | 27 | matches(tokenAddress: string): boolean { 28 | return arrayIncludesLoose(this.addresses, tokenAddress); 29 | } 30 | 31 | getTokens(): string[] { 32 | return this.addresses; 33 | } 34 | 35 | getPrice(tokenAddress: string, _priceLookup: PriceLookup, _block: BigInt): PriceLookupResult { 36 | if (!this.matches(tokenAddress)) { 37 | throw new Error( 38 | `Attempted to look up unsupported token ${this.contractLookup( 39 | tokenAddress, 40 | )} (${tokenAddress})`, 41 | ); 42 | } 43 | 44 | return { 45 | price: BigDecimal.fromString("1"), 46 | liquidity: BigDecimal.fromString("0"), // TODO consider setting liquidity 47 | }; 48 | } 49 | 50 | getTotalValue( 51 | _excludedTokens: string[], 52 | _priceLookup: PriceLookup, 53 | _block: BigInt, 54 | ): BigDecimal | null { 55 | return null; 56 | } 57 | 58 | getUnitPrice(_priceLookup: PriceLookup, _block: BigInt): BigDecimal | null { 59 | return null; 60 | } 61 | 62 | getBalance(_walletAddress: string, _block: BigInt): BigDecimal { 63 | return BigDecimal.zero(); 64 | } 65 | 66 | getUnderlyingTokenBalance(_walletAddress: string, _tokenAddress: string, _block: BigInt): BigDecimal { 67 | throw new Error("Method not implemented."); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /subgraphs/shared/src/price/PriceHandlerRemapping.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ContractNameLookup } from "../contracts/ContractLookup"; 4 | import { addressesEqual } from "../utils/StringHelper"; 5 | import { PriceHandler, PriceLookup, PriceLookupResult } from "./PriceHandler"; 6 | 7 | const CLASS = "PriceHandlerRemapping"; 8 | 9 | export class PriceHandlerRemapping implements PriceHandler { 10 | protected assetAddress: string; 11 | protected destinationTokenAddress: string; 12 | protected contractLookup: ContractNameLookup; 13 | 14 | constructor(assetAddress: string, destinationTokenAddress: string, contractLookup: ContractNameLookup) { 15 | this.assetAddress = assetAddress; 16 | this.destinationTokenAddress = destinationTokenAddress; 17 | this.contractLookup = contractLookup; 18 | } 19 | 20 | getId(): string { 21 | return this.assetAddress; 22 | } 23 | 24 | exists(): boolean { 25 | return true; 26 | } 27 | 28 | matches(tokenAddress: string): boolean { 29 | return addressesEqual(this.assetAddress, tokenAddress); 30 | } 31 | 32 | getTokens(): string[] { 33 | return [this.assetAddress]; 34 | } 35 | 36 | getPrice(tokenAddress: string, priceLookup: PriceLookup, block: BigInt): PriceLookupResult | null { 37 | // Look up the price of the destination token and return that 38 | return priceLookup(this.destinationTokenAddress, block, this.getId()); 39 | } 40 | 41 | getTotalValue(_excludedTokens: string[], _priceLookup: PriceLookup, _block: BigInt): BigDecimal | null { 42 | throw new Error(`${CLASS}: getTotalValue: not implemented`); 43 | } 44 | 45 | getUnitPrice(_priceLookup: PriceLookup, _block: BigInt): BigDecimal | null { 46 | throw new Error(`${CLASS}: getUnitPrice: not implemented`); 47 | } 48 | 49 | getBalance(_walletAddress: string, _block: BigInt): BigDecimal { 50 | throw new Error(`${CLASS}: getBalance: not implemented`); 51 | } 52 | 53 | getUnderlyingTokenBalance(_walletAddress: string, _tokenAddress: string, _block: BigInt): BigDecimal { 54 | throw new Error(`${CLASS}: getUnderlyingTokenBalance: not implemented`); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/TokenNative.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../generated/schema"; 4 | import { ContractNameLookup } from "../contracts/ContractLookup"; 5 | import { toDecimal } from "./Decimals"; 6 | import { createTokenRecord } from "./TokenRecordHelper"; 7 | import { getTokensForChain } from "./TokensForChain"; 8 | 9 | export type GetPrice = (tokenAddress: string, blockNumber: BigInt) => BigDecimal; 10 | 11 | /** 12 | * Get the native network curren balances for a given timestamp and block number 13 | * @param timestamp - The timestamp of the block 14 | * @param blockNumber - The block number 15 | * @param blockchain - The blockchain to get the native balances for 16 | * @returns An array of TokenRecord objects representing the native balances 17 | */ 18 | export function getNativeTokenBalances( 19 | timestamp: BigInt, 20 | blockNumber: BigInt, 21 | blockchain: string, 22 | wallets: string[], 23 | priceLookup: GetPrice, 24 | getContractName: ContractNameLookup, 25 | ): TokenRecord[] { 26 | const zeroAddress = Address.zero().toHexString().toLowerCase(); 27 | 28 | const rate = priceLookup(zeroAddress, blockNumber); 29 | const records: TokenRecord[] = []; 30 | 31 | const tokensForChain = getTokensForChain(blockchain); 32 | 33 | for (let i = 0; i < wallets.length; i++) { 34 | const balance = ethereum.getBalance(Address.fromString(wallets[i])); 35 | 36 | // skip if balance is 0 37 | if (balance.equals(BigInt.fromString("0"))) { 38 | continue; 39 | } 40 | 41 | const decimalBalance = toDecimal(balance); 42 | 43 | const record = createTokenRecord( 44 | timestamp, 45 | getContractName(zeroAddress), 46 | zeroAddress, 47 | getContractName(wallets[i]), 48 | wallets[i], 49 | rate, 50 | decimalBalance, 51 | blockNumber, 52 | true, 53 | tokensForChain, 54 | blockchain, 55 | ); 56 | log.info(`getNativeTokenBalances was created: ${wallets[i]}, record: ${record.id}`, []); 57 | if (!record) { 58 | log.info(`getNativeTokenBalances was not created: ${wallets[i]}`, []); 59 | continue; 60 | } 61 | records.push(record); 62 | } 63 | 64 | return records; 65 | } 66 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/contracts/CoolerLoansClearinghouse.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { createTokenRecord } from "../../../shared/src/utils/TokenRecordHelper"; 6 | import { CoolerLoansClearinghouse } from "../../generated/ProtocolMetrics/CoolerLoansClearinghouse"; 7 | import { getClearinghouseAddresses } from "../utils/Bophades"; 8 | import { BLOCKCHAIN, ERC20_DAI, ERC20_TOKENS, getContractName } from "../utils/Constants"; 9 | import { getUSDRate } from "../utils/Price"; 10 | 11 | /** 12 | * Generates records for the DAI receivables in the Clearinghouses. 13 | * 14 | * @param timestamp 15 | * @param blockNumber 16 | * @returns 17 | */ 18 | export function getClearinghouseReceivables(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 19 | const FUNC = "getClearinghouseReceivables"; 20 | const records: TokenRecord[] = []; 21 | 22 | const daiRate = getUSDRate(ERC20_DAI, blockNumber); 23 | const clearinghouses = getClearinghouseAddresses(blockNumber); 24 | 25 | for (let i = 0; i < clearinghouses.length; i++) { 26 | const clearinghouseAddress = clearinghouses[i]; 27 | 28 | // Grab the receivables from the clearinghouse 29 | const clearinghouseContract = CoolerLoansClearinghouse.bind(clearinghouseAddress); 30 | const receivablesResult = clearinghouseContract.try_principalReceivables(); 31 | 32 | if (receivablesResult.reverted) { 33 | continue; 34 | } 35 | 36 | const receivablesBalance = toDecimal(receivablesResult.value, 18); 37 | log.info(`{}: Cooler Loans Clearinghouse receivables balance: {}`, [FUNC, receivablesBalance.toString()]); 38 | 39 | records.push( 40 | createTokenRecord( 41 | timestamp, 42 | `${getContractName(ERC20_DAI)} - Borrowed Through Cooler Loans`, 43 | ERC20_DAI, 44 | getContractName(clearinghouseAddress.toHexString()), 45 | clearinghouseAddress.toHexString(), 46 | daiRate, 47 | receivablesBalance, 48 | blockNumber, 49 | true, // Considers DAI receivables as liquid 50 | ERC20_TOKENS, 51 | BLOCKCHAIN, 52 | ), 53 | ); 54 | } 55 | 56 | return records; 57 | } 58 | -------------------------------------------------------------------------------- /subgraphs/shared/src/price/PriceHandlerCustomMapping.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ContractNameLookup } from "../contracts/ContractLookup"; 4 | import { arrayIncludesLoose } from "../utils/ArrayHelper"; 5 | import { PriceHandler, PriceLookup, PriceLookupResult } from "./PriceHandler"; 6 | 7 | /** 8 | * Class for mapping the price of one or more tokens to the price of another. 9 | * 10 | * For example, pegging the price of sKLIMA to that of KLIMA. 11 | */ 12 | export class PriceHandlerCustomMapping implements PriceHandler { 13 | protected tokenAddress: string; 14 | protected mappedTokenAddresses: string[]; 15 | protected contractLookup: ContractNameLookup; 16 | 17 | constructor( 18 | tokenAddress: string, 19 | mappedTokenAddresses: string[], 20 | contractLookup: ContractNameLookup, 21 | ) { 22 | this.tokenAddress = tokenAddress; 23 | this.mappedTokenAddresses = mappedTokenAddresses; 24 | this.contractLookup = contractLookup; 25 | } 26 | 27 | getId(): string { 28 | return `${this.tokenAddress}-${this.mappedTokenAddresses.join("/")}`; 29 | } 30 | 31 | exists(): boolean { 32 | return true; 33 | } 34 | 35 | matches(tokenAddress: string): boolean { 36 | return arrayIncludesLoose(this.mappedTokenAddresses, tokenAddress); 37 | } 38 | 39 | getTokens(): string[] { 40 | return this.mappedTokenAddresses; 41 | } 42 | 43 | getPrice( 44 | tokenAddress: string, 45 | priceLookup: PriceLookup, 46 | block: BigInt, 47 | ): PriceLookupResult | null { 48 | return priceLookup(this.tokenAddress, block, null); 49 | } 50 | 51 | getTotalValue( 52 | _excludedTokens: string[], 53 | _priceLookup: PriceLookup, 54 | _block: BigInt, 55 | ): BigDecimal | null { 56 | // TODO implement 57 | return BigDecimal.zero(); 58 | } 59 | 60 | getUnitPrice(_priceLookup: PriceLookup, _block: BigInt): BigDecimal | null { 61 | // TODO implement 62 | return BigDecimal.zero(); 63 | } 64 | 65 | getBalance(_walletAddress: string, _block: BigInt): BigDecimal { 66 | // TODO implement 67 | return BigDecimal.zero(); 68 | } 69 | 70 | getUnderlyingTokenBalance(_walletAddress: string, _tokenAddress: string, _block: BigInt): BigDecimal { 71 | throw new Error("Method not implemented."); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/contracts/CoolerLoansV2Monocooler.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { createTokenRecord } from "../../../shared/src/utils/TokenRecordHelper"; 6 | import { COOLER_LOANS_V2_MONOCOOLER } from "../../../shared/src/Wallets"; 7 | import { CoolerLoansMonoCooler } from "../../generated/ProtocolMetrics/CoolerLoansMonoCooler"; 8 | import { 9 | BLOCKCHAIN, 10 | COOLER_LOANS_V2_MONOCOOLER_START_BLOCK, 11 | ERC20_DAI, 12 | ERC20_TOKENS, 13 | ERC20_USDS, 14 | getContractName, 15 | } from "../utils/Constants"; 16 | import { getUSDRate } from "../utils/Price"; 17 | 18 | /** 19 | * Generates records for the USDS receivables in the Cooler Loans V2 MonoCooler. 20 | * 21 | * @param timestamp 22 | * @param blockNumber 23 | * @returns 24 | */ 25 | export function getCoolerV2Receivables(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 26 | const FUNC = "getCoolerV2Receivables"; 27 | const records: TokenRecord[] = []; 28 | 29 | // If before the start block, return an empty array 30 | if (blockNumber.lt(BigInt.fromString(COOLER_LOANS_V2_MONOCOOLER_START_BLOCK))) { 31 | return records; 32 | } 33 | 34 | const daiRate = getUSDRate(ERC20_DAI, blockNumber); 35 | const coolerV2Monocooler = COOLER_LOANS_V2_MONOCOOLER; 36 | 37 | // Grab the receivables from the clearinghouse 38 | const coolerV2MonocoolerContract = CoolerLoansMonoCooler.bind(Address.fromString(coolerV2Monocooler)); 39 | const receivablesResult = coolerV2MonocoolerContract.totalDebt(); 40 | 41 | const receivablesBalance = toDecimal(receivablesResult, 18); 42 | log.info(`{}: Cooler Loans V2 MonoCooler receivables balance: {}`, [ 43 | FUNC, 44 | receivablesBalance.toString(), 45 | ]); 46 | 47 | records.push( 48 | createTokenRecord( 49 | timestamp, 50 | `${getContractName(ERC20_USDS)} - Borrowed Through Cooler Loans V2`, 51 | ERC20_USDS, 52 | getContractName(COOLER_LOANS_V2_MONOCOOLER), 53 | COOLER_LOANS_V2_MONOCOOLER, 54 | daiRate, 55 | receivablesBalance, 56 | blockNumber, 57 | true, // Considers USDS receivables as liquid 58 | ERC20_TOKENS, 59 | BLOCKCHAIN, 60 | ), 61 | ); 62 | 63 | return records; 64 | } 65 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/PriceChainlink.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 4 | import { ChainlinkPriceFeed } from "../../generated/ProtocolMetrics/ChainlinkPriceFeed"; 5 | import { ERC20_ADAI, ERC20_DAI, ERC20_FRAX, ERC20_LUSD, ERC20_USDC, ERC20_USDE, ERC20_USDS, ERC20_WETH } from "./Constants"; 6 | 7 | const tokenPriceFeedMap: Map = new Map(); 8 | tokenPriceFeedMap.set(ERC20_ADAI, "0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9".toLowerCase()); 9 | tokenPriceFeedMap.set(ERC20_DAI, "0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9".toLowerCase()); 10 | tokenPriceFeedMap.set(ERC20_FRAX, "0xb9e1e3a9feff48998e45fa90847ed4d467e8bcfd".toLowerCase()); 11 | tokenPriceFeedMap.set(ERC20_LUSD, "0x3D7aE7E594f2f2091Ad8798313450130d0Aba3a0".toLowerCase()); 12 | tokenPriceFeedMap.set(ERC20_USDC, "0x8fffffd4afb6115b954bd326cbe7b4ba576818f6".toLowerCase()); 13 | tokenPriceFeedMap.set(ERC20_USDE, "0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961".toLowerCase()); 14 | tokenPriceFeedMap.set(ERC20_USDS, "0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9".toLowerCase()); // Use the DAI price feed for USDS as there is not one for USDS 15 | tokenPriceFeedMap.set(ERC20_WETH, "0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419".toLowerCase()); 16 | 17 | export function getPriceFeedTokens(): string[] { 18 | return tokenPriceFeedMap.keys(); 19 | } 20 | 21 | export function getPriceFeed(token: string): string | null { 22 | const tokenLower = token.toLowerCase(); 23 | if (!tokenPriceFeedMap.has(tokenLower)) { 24 | return null; 25 | } 26 | 27 | return tokenPriceFeedMap.get(tokenLower); 28 | } 29 | 30 | export function getPriceFeedValue(tokenAddress: string): BigDecimal | null { 31 | const tokenAddressLower = tokenAddress.toLowerCase(); 32 | if (!tokenPriceFeedMap.has(tokenAddressLower)) { 33 | return null; 34 | } 35 | 36 | const priceFeedAddress = tokenPriceFeedMap.get(tokenAddressLower); 37 | const priceFeed = ChainlinkPriceFeed.bind(Address.fromString(priceFeedAddress)); 38 | const decimalsResult = priceFeed.try_decimals(); 39 | const answerResult = priceFeed.try_latestAnswer(); 40 | 41 | if (decimalsResult.reverted || answerResult.reverted) { 42 | return null; 43 | } 44 | 45 | return toDecimal(answerResult.value, decimalsResult.value); 46 | } -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/contracts/Silo.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenSupply } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { createTokenSupply,TYPE_LENDING } from "../../../shared/src/utils/TokenSupplyHelper"; 6 | import { ERC20 } from "../../generated/TokenRecords-arbitrum/ERC20"; 7 | import { ERC20_OHM } from "./Constants"; 8 | import { getContractName, getWalletAddressesForContract } from "./Contracts"; 9 | 10 | // Hard-coding this for now. If we wanted this to be generalisable, we would use the Silo Repository contract. 11 | const SILO_OHM_COLLATERAL_TOKEN = "0xD8102963c400fEDBbc23Fe92f1b09c0C561e77Ae"; 12 | 13 | export function getSiloSupply(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 14 | const records: TokenSupply[] = []; 15 | 16 | const collateralTokenContract = ERC20.bind(Address.fromString(SILO_OHM_COLLATERAL_TOKEN)); 17 | const collateralTokenDecimalsResult = collateralTokenContract.try_decimals(); 18 | if (collateralTokenDecimalsResult.reverted) { 19 | return records; 20 | } 21 | const collateralTokenDecimals = collateralTokenDecimalsResult.value; 22 | 23 | // Iterate over wallets to find the balances 24 | const wallets = getWalletAddressesForContract(SILO_OHM_COLLATERAL_TOKEN); 25 | for (let i = 0; i < wallets.length; i++) { 26 | const currentWallet = wallets[i]; 27 | 28 | const balanceResult = collateralTokenContract.try_balanceOf(Address.fromString(currentWallet)); 29 | if (balanceResult.reverted) { 30 | continue; 31 | } 32 | 33 | const balance = toDecimal(balanceResult.value, collateralTokenDecimals); 34 | if (balance.equals(BigDecimal.zero())) { 35 | continue; 36 | } 37 | 38 | log.info("getSiloSupply: Silo OHM balance {} for wallet {}", [balance.toString(), getContractName(currentWallet)]); 39 | records.push( 40 | createTokenSupply( 41 | timestamp, 42 | getContractName(ERC20_OHM), 43 | ERC20_OHM, 44 | "Silo", 45 | SILO_OHM_COLLATERAL_TOKEN, 46 | getContractName(currentWallet), 47 | currentWallet, 48 | TYPE_LENDING, 49 | balance, 50 | blockNumber, 51 | -1, // Subtract, as this represents OHM taken out of supply 52 | ), 53 | ); 54 | } 55 | 56 | return records; 57 | } -------------------------------------------------------------------------------- /subgraphs/price-snapshot/tests/Math.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal } from "@graphprotocol/graph-ts"; 2 | import { assert, describe, test } from "matchstick-as"; 3 | 4 | import { getDelta, getStandardDeviation } from "../src/helpers/Math"; 5 | 6 | describe("getDelta", () => { 7 | test("null previous value", () => { 8 | const currentValue: BigDecimal = BigDecimal.fromString("10.01"); 9 | const previousValue: BigDecimal | null = null; 10 | const delta: BigDecimal | null = getDelta(currentValue, previousValue); 11 | 12 | assert.assertTrue(delta === null ? true : false); 13 | }); 14 | 15 | test("zero previous value", () => { 16 | const currentValue: BigDecimal = BigDecimal.fromString("10.01"); 17 | const previousValue: BigDecimal = BigDecimal.fromString("0"); 18 | const delta: BigDecimal | null = getDelta(currentValue, previousValue); 19 | 20 | assert.assertTrue(delta === null ? true : false); 21 | }); 22 | 23 | test("delta is accurate", () => { 24 | const currentValue: BigDecimal = BigDecimal.fromString("10"); 25 | const previousValue: BigDecimal = BigDecimal.fromString("8"); 26 | const delta: BigDecimal | null = getDelta(currentValue, previousValue); 27 | 28 | // (10/8) - 1 = 0.25 29 | const expectedDelta = currentValue.div(previousValue).minus(BigDecimal.fromString("1")); 30 | 31 | assert.stringEquals(expectedDelta.toString(), delta ? delta.toString() : ""); 32 | }); 33 | }); 34 | 35 | describe("getStandardDeviation", () => { 36 | test("missing value", () => { 37 | const value = getStandardDeviation([BigDecimal.fromString("1"), BigDecimal.fromString("1.1"), BigDecimal.fromString("1.2")], 4); 38 | 39 | assert.assertTrue(value === null ? true : false); 40 | }); 41 | 42 | test("standard deviation is accurate", () => { 43 | const value = getStandardDeviation([BigDecimal.fromString("1"), BigDecimal.fromString("1.1"), BigDecimal.fromString("1.2")], 3); 44 | 45 | // mean = 1.1 46 | // squared difference from mean = [(1-1.1)^2, (1.1-1.1)^2, (1.2-1.1)^2] 47 | // mean of squared differences = 0.0066666667 48 | // sqrt = 0.0816496581 49 | const expectedValue = "0.0816496581"; 50 | 51 | assert.stringEquals(expectedValue.toString().slice(0, 11), value ? value.toString().slice(0, 11) : ""); 52 | }); 53 | }); -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/Silo.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenSupply } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { createTokenSupply,TYPE_LENDING } from "../../../shared/src/utils/TokenSupplyHelper"; 6 | import { ERC20 } from "../../generated/ProtocolMetrics/ERC20"; 7 | import { ERC20_OHM_V2, getContractName } from "./Constants"; 8 | import { getWalletAddressesForContract } from "./ProtocolAddresses"; 9 | 10 | // Hard-coding this for now. If we wanted this to be generalisable, we would use the Silo Repository contract. 11 | const SILO_OHM_COLLATERAL_TOKEN = "0x907136B74abA7D5978341eBA903544134A66B065"; 12 | 13 | export function getSiloSupply(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 14 | const records: TokenSupply[] = []; 15 | 16 | const collateralTokenContract = ERC20.bind(Address.fromString(SILO_OHM_COLLATERAL_TOKEN)); 17 | const collateralTokenDecimalsResult = collateralTokenContract.try_decimals(); 18 | if (collateralTokenDecimalsResult.reverted) { 19 | return records; 20 | } 21 | const collateralTokenDecimals = collateralTokenDecimalsResult.value; 22 | 23 | // Iterate over wallets to find the balances 24 | const wallets = getWalletAddressesForContract(SILO_OHM_COLLATERAL_TOKEN, blockNumber); 25 | for (let i = 0; i < wallets.length; i++) { 26 | const currentWallet = wallets[i]; 27 | 28 | const balanceResult = collateralTokenContract.try_balanceOf(Address.fromString(currentWallet)); 29 | if (balanceResult.reverted) { 30 | continue; 31 | } 32 | 33 | const balance = toDecimal(balanceResult.value, collateralTokenDecimals); 34 | if (balance.equals(BigDecimal.zero())) { 35 | continue; 36 | } 37 | 38 | log.info("getSiloSupply: Silo OHM balance {} for wallet {}", [balance.toString(), getContractName(currentWallet)]); 39 | records.push( 40 | createTokenSupply( 41 | timestamp, 42 | getContractName(ERC20_OHM_V2), 43 | ERC20_OHM_V2, 44 | "Silo", 45 | SILO_OHM_COLLATERAL_TOKEN, 46 | getContractName(currentWallet), 47 | currentWallet, 48 | TYPE_LENDING, 49 | balance, 50 | blockNumber, 51 | -1, // Subtract, as this represents OHM taken out of supply 52 | ), 53 | ); 54 | } 55 | 56 | return records; 57 | } -------------------------------------------------------------------------------- /subgraphs/base/src/contracts/Contracts.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20 } from "../../../shared/generated/Price/ERC20"; 4 | import { TokenRecord } from "../../../shared/generated/schema"; 5 | import { getERC20TokenRecordFromWallet } from "../../../shared/src/contracts/ERC20"; 6 | import { BLOCKCHAIN, CONTRACT_ABBREVIATION_MAP, CONTRACT_NAME_MAP, ERC20_TOKENS_BASE, getWalletAddressesForContract } from "./Constants"; 7 | 8 | export function getContractName( 9 | contractAddress: string, 10 | suffix: string | null = null, 11 | abbreviation: string | null = null, 12 | ): string { 13 | const contractAddressLower = contractAddress.toLowerCase(); 14 | 15 | const contractName = CONTRACT_NAME_MAP.has(contractAddressLower) 16 | ? CONTRACT_NAME_MAP.get(contractAddressLower) 17 | : contractAddressLower; 18 | 19 | // Suffix 20 | const contractSuffix = suffix ? ` - ${suffix}` : ""; 21 | 22 | // Abbreviation 23 | const contractAbbreviation = abbreviation 24 | ? ` (${abbreviation})` 25 | : CONTRACT_ABBREVIATION_MAP.has(contractAddressLower) 26 | ? ` (${CONTRACT_ABBREVIATION_MAP.get(contractAddressLower)})` 27 | : ""; 28 | 29 | return `${contractName}${contractSuffix}${contractAbbreviation}`; 30 | } 31 | 32 | /** 33 | * Fetches the balances of the given ERC20 token from 34 | * the wallets defined in {getWalletAddressesForContract}. 35 | * 36 | * @param metricName The name of the current metric, which is used for entity ids 37 | * @param contractAddress ERC20 contract address 38 | * @param contract ERC20 contract 39 | * @param rate the unit price/rate of the token 40 | * @param blockNumber the current block number 41 | * @returns TokenRecord array 42 | */ 43 | export function getERC20TokenRecordsFromWallets( 44 | timestamp: BigInt, 45 | contractAddress: string, 46 | contract: ERC20, 47 | rate: BigDecimal, 48 | blockNumber: BigInt, 49 | ): TokenRecord[] { 50 | const records: TokenRecord[] = []; 51 | const wallets = getWalletAddressesForContract(contractAddress); 52 | 53 | for (let i = 0; i < wallets.length; i++) { 54 | const record = getERC20TokenRecordFromWallet( 55 | timestamp, 56 | contractAddress, 57 | wallets[i], 58 | contract, 59 | rate, 60 | blockNumber, 61 | getContractName, 62 | ERC20_TOKENS_BASE, 63 | BLOCKCHAIN, 64 | ); 65 | if (!record) continue; 66 | 67 | records.push(record); 68 | } 69 | 70 | return records; 71 | } -------------------------------------------------------------------------------- /subgraphs/berachain/src/contracts/Contracts.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20 } from "../../../shared/generated/Price/ERC20"; 4 | import { TokenRecord } from "../../../shared/generated/schema"; 5 | import { getERC20TokenRecordFromWallet } from "../../../shared/src/contracts/ERC20"; 6 | import { BLOCKCHAIN, CONTRACT_ABBREVIATION_MAP, CONTRACT_NAME_MAP, ERC20_TOKENS_BERACHAIN, getWalletAddressesForContract } from "./Constants"; 7 | 8 | export function getContractName( 9 | contractAddress: string, 10 | suffix: string | null = null, 11 | abbreviation: string | null = null, 12 | ): string { 13 | const contractAddressLower = contractAddress.toLowerCase(); 14 | 15 | const contractName = CONTRACT_NAME_MAP.has(contractAddressLower) 16 | ? CONTRACT_NAME_MAP.get(contractAddressLower) 17 | : contractAddressLower; 18 | 19 | // Suffix 20 | const contractSuffix = suffix ? ` - ${suffix}` : ""; 21 | 22 | // Abbreviation 23 | const contractAbbreviation = abbreviation 24 | ? ` (${abbreviation})` 25 | : CONTRACT_ABBREVIATION_MAP.has(contractAddressLower) 26 | ? ` (${CONTRACT_ABBREVIATION_MAP.get(contractAddressLower)})` 27 | : ""; 28 | 29 | return `${contractName}${contractSuffix}${contractAbbreviation}`; 30 | } 31 | 32 | /** 33 | * Fetches the balances of the given ERC20 token from 34 | * the wallets defined in {getWalletAddressesForContract}. 35 | * 36 | * @param metricName The name of the current metric, which is used for entity ids 37 | * @param contractAddress ERC20 contract address 38 | * @param contract ERC20 contract 39 | * @param rate the unit price/rate of the token 40 | * @param blockNumber the current block number 41 | * @returns TokenRecord array 42 | */ 43 | export function getERC20TokenRecordsFromWallets( 44 | timestamp: BigInt, 45 | contractAddress: string, 46 | contract: ERC20, 47 | rate: BigDecimal, 48 | blockNumber: BigInt, 49 | ): TokenRecord[] { 50 | const records: TokenRecord[] = []; 51 | const wallets = getWalletAddressesForContract(contractAddress); 52 | 53 | for (let i = 0; i < wallets.length; i++) { 54 | const record = getERC20TokenRecordFromWallet( 55 | timestamp, 56 | contractAddress, 57 | wallets[i], 58 | contract, 59 | rate, 60 | blockNumber, 61 | getContractName, 62 | ERC20_TOKENS_BERACHAIN, 63 | BLOCKCHAIN, 64 | ); 65 | if (!record) continue; 66 | 67 | records.push(record); 68 | } 69 | 70 | return records; 71 | } -------------------------------------------------------------------------------- /subgraphs/ethereum/src/protocolMetrics/TreasuryMetrics.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { BUYBACK_MS } from "../../../shared/src/Wallets"; 5 | import { ERC20_GOHM, ERC20_OHM_V1, ERC20_OHM_V2, OHM_IN_MARKET_VALUE_BLOCK } from "../utils/Constants"; 6 | 7 | /** 8 | * OHM price * circulating supply 9 | */ 10 | export function getMarketCap(ohmPrice: BigDecimal, circulatingSupply: BigDecimal): BigDecimal { 11 | return ohmPrice.times(circulatingSupply); 12 | } 13 | 14 | function _isTokenOHM(tokenAddress: string): boolean { 15 | const tokenLower = tokenAddress.toLowerCase(); 16 | 17 | return tokenLower == ERC20_OHM_V1 || tokenLower == ERC20_OHM_V2 || tokenLower == ERC20_GOHM; 18 | } 19 | 20 | /** 21 | * Includes all assets in the treasury 22 | */ 23 | export function getTreasuryMarketValue(tokenRecords: TokenRecord[]): BigDecimal { 24 | let total = BigDecimal.zero(); 25 | 26 | for (let i = 0; i < tokenRecords.length; i++) { 27 | const tokenRecord = tokenRecords[i]; 28 | 29 | // Ignore if an OHM token and: 30 | // - not in the buyback MS, or 31 | // - it is before the inclusion block 32 | if (_isTokenOHM(tokenRecord.tokenAddress) && 33 | ( 34 | tokenRecord.sourceAddress.toLowerCase() != BUYBACK_MS.toLowerCase() 35 | || 36 | tokenRecord.block.lt(OHM_IN_MARKET_VALUE_BLOCK) 37 | )) { 38 | continue; 39 | } 40 | 41 | total = total.plus(tokenRecord.value); 42 | } 43 | 44 | return total; 45 | } 46 | 47 | /** 48 | * Includes all liquid assets in the treasury, excluding the value of OHM 49 | */ 50 | export function getTreasuryLiquidBacking(tokenRecords: TokenRecord[]): BigDecimal { 51 | let total = BigDecimal.zero(); 52 | 53 | for (let i = 0; i < tokenRecords.length; i++) { 54 | const tokenRecord = tokenRecords[i]; 55 | if (!tokenRecord.isLiquid) { 56 | continue; 57 | } 58 | 59 | total = total.plus(tokenRecord.valueExcludingOhm); 60 | } 61 | 62 | return total; 63 | } 64 | 65 | export function getTreasuryLiquidBackingPerOhmFloating(liquidBacking: BigDecimal, ohmFloatingSupply: BigDecimal): BigDecimal { 66 | return liquidBacking.div(ohmFloatingSupply); 67 | } 68 | 69 | export function getTreasuryLiquidBackingPerGOhmSynthetic(liquidBacking: BigDecimal, gOhmSupply: BigDecimal): BigDecimal { 70 | return liquidBacking.div(gOhmSupply); 71 | } 72 | -------------------------------------------------------------------------------- /subgraphs/base/src/treasury/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { getERC20 } from "../../../shared/src/contracts/ERC20"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getTokensInCategory } from "../../../shared/src/utils/TokenRecordHelper"; 7 | import { ERC20_TOKENS_BASE } from "../contracts/Constants"; 8 | import { getContractName, getERC20TokenRecordsFromWallets } from "../contracts/Contracts"; 9 | import { getPrice } from "../price/PriceLookup"; 10 | 11 | /** 12 | * Returns the token records for a given token. This includes: 13 | * - Wallets 14 | * - Allocators 15 | * 16 | * @param contractAddress the address of the ERC20 contract 17 | * @param blockNumber the current block 18 | * @returns TokenRecord array 19 | */ 20 | function getTokenBalance( 21 | timestamp: BigInt, 22 | contractAddress: string, 23 | blockNumber: BigInt, 24 | ): TokenRecord[] { 25 | const contractName = getContractName(contractAddress); 26 | log.info("getTokenBalance: Calculating balance for {} ({}) at block number {}", [ 27 | contractName, 28 | contractAddress, 29 | blockNumber.toString(), 30 | ]); 31 | const records: TokenRecord[] = []; 32 | const contract = getERC20(contractAddress, blockNumber); 33 | if (!contract) { 34 | log.info("getTokenBalance: Skipping ERC20 contract {} that returned empty at block {}", [ 35 | getContractName(contractAddress), 36 | blockNumber.toString(), 37 | ]); 38 | return records; 39 | } 40 | 41 | const rate = getPrice(contractAddress, blockNumber); 42 | 43 | // Standard ERC20 44 | pushTokenRecordArray( 45 | records, 46 | getERC20TokenRecordsFromWallets(timestamp, contractAddress, contract, rate, blockNumber), 47 | ); 48 | 49 | return records; 50 | } 51 | 52 | /** 53 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 54 | * 55 | * @param timestamp 56 | * @param blockNumber the current block 57 | * @returns TokenRecord array 58 | */ 59 | export function getTokenBalances( 60 | timestamp: BigInt, 61 | category: string, 62 | blockNumber: BigInt, 63 | ): TokenRecord[] { 64 | const records: TokenRecord[] = []; 65 | 66 | const categoryTokens = getTokensInCategory(category, ERC20_TOKENS_BASE); 67 | for (let i = 0; i < categoryTokens.length; i++) { 68 | pushTokenRecordArray(records, getTokenBalance(timestamp, categoryTokens[i].getAddress(), blockNumber)); 69 | } 70 | 71 | return records; 72 | } 73 | -------------------------------------------------------------------------------- /subgraphs/fantom/src/treasury/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { getERC20 } from "../../../shared/src/contracts/ERC20"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getTokensInCategory } from "../../../shared/src/utils/TokenRecordHelper"; 7 | import { ERC20_TOKENS_FANTOM } from "../contracts/Constants"; 8 | import { getContractName, getERC20TokenRecordsFromWallets } from "../contracts/Contracts"; 9 | import { getPrice } from "../price/PriceLookup"; 10 | 11 | /** 12 | * Returns the token records for a given token. This includes: 13 | * - Wallets 14 | * - Allocators 15 | * 16 | * @param contractAddress the address of the ERC20 contract 17 | * @param blockNumber the current block 18 | * @returns TokenRecord array 19 | */ 20 | function getTokenBalance( 21 | timestamp: BigInt, 22 | contractAddress: string, 23 | blockNumber: BigInt, 24 | ): TokenRecord[] { 25 | const contractName = getContractName(contractAddress); 26 | log.info("getTokenBalance: Calculating balance for {} ({}) at block number {}", [ 27 | contractName, 28 | contractAddress, 29 | blockNumber.toString(), 30 | ]); 31 | const records: TokenRecord[] = []; 32 | const contract = getERC20(contractAddress, blockNumber); 33 | if (!contract) { 34 | log.info("getTokenBalance: Skipping ERC20 contract {} that returned empty at block {}", [ 35 | getContractName(contractAddress), 36 | blockNumber.toString(), 37 | ]); 38 | return records; 39 | } 40 | 41 | const rate = getPrice(contractAddress, blockNumber); 42 | 43 | // Standard ERC20 44 | pushTokenRecordArray( 45 | records, 46 | getERC20TokenRecordsFromWallets(timestamp, contractAddress, contract, rate, blockNumber), 47 | ); 48 | 49 | return records; 50 | } 51 | 52 | /** 53 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 54 | * 55 | * @param timestamp 56 | * @param blockNumber the current block 57 | * @returns TokenRecord array 58 | */ 59 | export function getTokenBalances( 60 | timestamp: BigInt, 61 | category: string, 62 | blockNumber: BigInt, 63 | ): TokenRecord[] { 64 | const records: TokenRecord[] = []; 65 | 66 | const categoryTokens = getTokensInCategory(category, ERC20_TOKENS_FANTOM); 67 | for (let i = 0; i < categoryTokens.length; i++) { 68 | pushTokenRecordArray(records, getTokenBalance(timestamp, categoryTokens[i].getAddress(), blockNumber)); 69 | } 70 | 71 | return records; 72 | } 73 | -------------------------------------------------------------------------------- /subgraphs/polygon/src/treasury/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { getERC20 } from "../../../shared/src/contracts/ERC20"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getTokensInCategory } from "../../../shared/src/utils/TokenRecordHelper"; 7 | import { ERC20_TOKENS_POLYGON } from "../contracts/Constants"; 8 | import { getContractName, getERC20TokenRecordsFromWallets } from "../contracts/Contracts"; 9 | import { getPrice } from "../price/PriceLookup"; 10 | 11 | /** 12 | * Returns the token records for a given token. This includes: 13 | * - Wallets 14 | * - Allocators 15 | * 16 | * @param contractAddress the address of the ERC20 contract 17 | * @param blockNumber the current block 18 | * @returns TokenRecord array 19 | */ 20 | function getTokenBalance( 21 | timestamp: BigInt, 22 | contractAddress: string, 23 | blockNumber: BigInt, 24 | ): TokenRecord[] { 25 | const contractName = getContractName(contractAddress); 26 | log.info("getTokenBalance: Calculating balance for {} ({}) at block number {}", [ 27 | contractName, 28 | contractAddress, 29 | blockNumber.toString(), 30 | ]); 31 | const records: TokenRecord[] = []; 32 | const contract = getERC20(contractAddress, blockNumber); 33 | if (!contract) { 34 | log.info("getTokenBalance: Skipping ERC20 contract {} that returned empty at block {}", [ 35 | getContractName(contractAddress), 36 | blockNumber.toString(), 37 | ]); 38 | return records; 39 | } 40 | 41 | const rate = getPrice(contractAddress, blockNumber); 42 | 43 | // Standard ERC20 44 | pushTokenRecordArray( 45 | records, 46 | getERC20TokenRecordsFromWallets(timestamp, contractAddress, contract, rate, blockNumber), 47 | ); 48 | 49 | return records; 50 | } 51 | 52 | /** 53 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 54 | * 55 | * @param timestamp 56 | * @param blockNumber the current block 57 | * @returns TokenRecord array 58 | */ 59 | export function getTokenBalances( 60 | timestamp: BigInt, 61 | category: string, 62 | blockNumber: BigInt, 63 | ): TokenRecord[] { 64 | const records: TokenRecord[] = []; 65 | 66 | const categoryTokens = getTokensInCategory(category, ERC20_TOKENS_POLYGON); 67 | for (let i = 0; i < categoryTokens.length; i++) { 68 | pushTokenRecordArray(records, getTokenBalance(timestamp, categoryTokens[i].getAddress(), blockNumber)); 69 | } 70 | 71 | return records; 72 | } 73 | -------------------------------------------------------------------------------- /subgraphs/berachain/src/price/PriceLookup.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { PriceLookupResult } from "../../../shared/src/price/PriceHandler"; 4 | import { getUSDRate } from "../../../shared/src/price/PriceRouter"; 5 | import { getContractName } from "../contracts/Contracts"; 6 | import { PRICE_HANDLERS } from "../contracts/LiquidityConstants"; 7 | import { getBaseTokenRate, isBaseToken } from "./PriceBase"; 8 | 9 | /** 10 | * Internal function to determine the price, using the shared price functions. 11 | * 12 | * It simply injects {HANDLERS}, so that {getUSDRate} can operate. 13 | * 14 | * @param tokenAddress 15 | * @param block 16 | * @param currentPool 17 | * @returns 18 | */ 19 | export function getPriceRecursive( 20 | tokenAddress: string, 21 | block: BigInt, 22 | currentPool: string | null, 23 | ): PriceLookupResult | null { 24 | const FUNC = "getPriceRecursive"; 25 | 26 | /** 27 | * Check for a base token in this function, instead of getPrice, so that recursive checks can use price feeds. 28 | */ 29 | if (isBaseToken(tokenAddress)) { 30 | const usdRate = getBaseTokenRate(Address.fromString(tokenAddress), block); 31 | return { 32 | price: usdRate, 33 | liquidity: new BigDecimal(BigInt.fromU64(U64.MAX_VALUE)) 34 | } 35 | } 36 | 37 | log.info("{}: Determining price for {} ({}) and current pool id {}", [FUNC, getContractName(tokenAddress), tokenAddress, currentPool ? currentPool : ""]); 38 | return getUSDRate(tokenAddress, PRICE_HANDLERS, getPriceRecursive, block, currentPool); 39 | } 40 | 41 | /** 42 | * External-facing function that determines the price of {tokenAddress}. 43 | * 44 | * @param tokenAddress 45 | * @param block 46 | * @returns BigDecimal 47 | * @throws Error if a price cannot be found, so that the subgraph indexing fails quickly 48 | */ 49 | export function getPrice(tokenAddress: string, block: BigInt): BigDecimal { 50 | const FUNC = "getPrice"; 51 | log.info(`${FUNC}: Determining price for ${getContractName(tokenAddress)} (${tokenAddress}) at block ${block.toString()}`, []); 52 | 53 | const priceResult = getPriceRecursive(tokenAddress, block, null); 54 | 55 | if (priceResult === null) { 56 | log.warning("{}: Unable to determine price for token {} ({}) at block {}", [FUNC, getContractName(tokenAddress), tokenAddress, block.toString()]); 57 | return BigDecimal.zero(); 58 | } 59 | 60 | log.info("{}: Price for {} ({}) at block {} was: {}", [FUNC, getContractName(tokenAddress), tokenAddress, block.toString(), priceResult.price.toString()]); 61 | return priceResult.price; 62 | } 63 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/treasury/Assets.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, ethereum, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord, TokenSupply } from "../../../shared/generated/schema"; 4 | import { 5 | TokenCategoryStable, 6 | TokenCategoryVolatile, 7 | } from "../../../shared/src/contracts/TokenDefinition"; 8 | import { pushTokenRecordArray, pushTokenSupplyArray } from "../../../shared/src/utils/ArrayHelper"; 9 | import { NewRound } from "../../generated/TokenRecords-arbitrum/ChainlinkAggregator"; 10 | import { getLendingMarketOHMRecords, getProtocolOwnedLiquiditySupplyRecords, getTotalSupply, getTreasuryOHMRecords } from "./OhmCalculations"; 11 | import { getOwnedLiquidityBalances } from "./OwnedLiquidity"; 12 | import { getTokenBalances } from "./TokenBalances"; 13 | 14 | function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 15 | const records: TokenRecord[] = []; 16 | 17 | pushTokenRecordArray( 18 | records, 19 | getTokenBalances(timestamp, TokenCategoryStable, blockNumber), 20 | ); 21 | 22 | 23 | pushTokenRecordArray( 24 | records, 25 | getTokenBalances(timestamp, TokenCategoryVolatile, blockNumber) 26 | ); 27 | 28 | pushTokenRecordArray( 29 | records, 30 | getOwnedLiquidityBalances(timestamp, blockNumber) 31 | ); 32 | 33 | return records; 34 | } 35 | 36 | function generateTokenSupplies(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 37 | const records: TokenSupply[] = []; 38 | 39 | // Total supply 40 | pushTokenSupplyArray( 41 | records, 42 | getTotalSupply(timestamp, blockNumber), 43 | ); 44 | 45 | // Treasury OHM 46 | pushTokenSupplyArray( 47 | records, 48 | getTreasuryOHMRecords(timestamp, blockNumber), 49 | ); 50 | 51 | // POL 52 | pushTokenSupplyArray( 53 | records, 54 | getProtocolOwnedLiquiditySupplyRecords(timestamp, blockNumber), 55 | ); 56 | 57 | // Lending markets 58 | pushTokenSupplyArray( 59 | records, 60 | getLendingMarketOHMRecords(timestamp, blockNumber), 61 | ); 62 | 63 | return records; 64 | } 65 | 66 | export function handleEvent(event: NewRound): void { 67 | const block = event.block; 68 | 69 | log.debug("handleEvent: *** Indexing block {}", [block.number.toString()]); 70 | generateTokenRecords(block.timestamp, block.number); 71 | generateTokenSupplies(block.timestamp, block.number); 72 | } 73 | 74 | export function handleBlock(block: ethereum.Block): void { 75 | log.debug("handleBlock: *** Indexing block {}", [block.number.toString()]); 76 | generateTokenRecords(block.timestamp, block.number); 77 | generateTokenSupplies(block.timestamp, block.number); 78 | } 79 | -------------------------------------------------------------------------------- /subgraphs/shared/src/utils/TokenSupplyHelper.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, Bytes } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenSupply } from "../../generated/schema"; 4 | import { getISO8601DateStringFromTimestamp } from "./DateHelper"; 5 | 6 | export const TYPE_BONDS_DEPOSITS = "OHM Bonds (Burnable Deposits)"; 7 | export const TYPE_BONDS_PREMINTED = "OHM Bonds (Pre-minted)"; 8 | export const TYPE_BONDS_VESTING_DEPOSITS = "OHM Bonds (Vesting Deposits)"; 9 | export const TYPE_BONDS_VESTING_TOKENS = "OHM Bonds (Vesting Tokens)"; 10 | export const TYPE_BOOSTED_LIQUIDITY_VAULT = "Boosted Liquidity Vault"; 11 | export const TYPE_LENDING = "Lending"; 12 | export const TYPE_LIQUIDITY = "Liquidity"; 13 | export const TYPE_OFFSET = "Manual Offset"; 14 | export const TYPE_TOTAL_SUPPLY = "Total Supply"; 15 | export const TYPE_TREASURY = "Treasury"; 16 | 17 | /** 18 | * Helper function to create a new TokenRecord. 19 | * 20 | * This function generates an id that should be unique, 21 | * and saves the record. 22 | * @returns 23 | */ 24 | export function createTokenSupply( 25 | timestamp: BigInt, 26 | tokenName: string, 27 | tokenAddress: string, 28 | poolName: string | null, 29 | poolAddress: string | null, 30 | sourceName: string | null, 31 | sourceAddress: string | null, 32 | type: string, 33 | balance: BigDecimal, 34 | blockNumber: BigInt, 35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 36 | multiplier: i32 = 1, 37 | ): TokenSupply { 38 | const dateString = getISO8601DateStringFromTimestamp(timestamp); 39 | 40 | const poolNameNotNull: string = poolName !== null ? poolName : "Unknown Pool"; 41 | const sourceNameNotNull: string = sourceName !== null ? sourceName : ""; 42 | 43 | // YYYY-MM-DD///// 44 | const recordId = Bytes.fromUTF8(dateString).concatI32(blockNumber.toI32()).concat(Bytes.fromUTF8(tokenName)).concat(Bytes.fromUTF8(type)).concat(Bytes.fromUTF8(poolNameNotNull)).concat(Bytes.fromUTF8(sourceNameNotNull)); `${dateString}/${tokenName}/${type}/${poolNameNotNull}/${sourceNameNotNull}`; 45 | const record = new TokenSupply(recordId); 46 | 47 | record.block = blockNumber; 48 | record.date = dateString; 49 | record.timestamp = timestamp; 50 | record.token = tokenName; 51 | record.tokenAddress = tokenAddress; 52 | record.source = sourceName; 53 | record.sourceAddress = sourceAddress; 54 | record.pool = poolName; 55 | record.poolAddress = poolAddress; 56 | record.type = type; 57 | record.balance = balance; 58 | record.supplyBalance = balance.times(BigDecimal.fromString(multiplier.toString())); 59 | 60 | record.save(); 61 | 62 | return record; 63 | } 64 | -------------------------------------------------------------------------------- /bin/subgraph/src/subgraphs/shared/results.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; 2 | import { dirname } from "path"; 3 | 4 | import { TokenRecord, TokenSupply } from "../../subgraph"; 5 | 6 | export type ComparisonResults = { 7 | latestDate?: string; 8 | branches: { 9 | base?: { 10 | blockNumber?: string; 11 | subgraphId: string; 12 | }; 13 | branch?: { 14 | blockNumber?: string; 15 | subgraphId: string; 16 | }; 17 | }; 18 | records: { 19 | tokenRecords: { 20 | base?: TokenRecord[]; 21 | branch?: TokenRecord[]; 22 | }; 23 | tokenSupplies: { 24 | base?: TokenSupply[]; 25 | branch?: TokenSupply[]; 26 | }; 27 | }; 28 | results: { 29 | marketValue?: { 30 | base: string; 31 | branch: string; 32 | diff: string; 33 | result: boolean; 34 | output: string; // Markdown 35 | }; 36 | liquidBacking?: { 37 | base: string; 38 | branch: string; 39 | diff: string; 40 | result: boolean; 41 | output: string; // Markdown 42 | }; 43 | marketValueCheck?: { 44 | marketValueTotal: string; 45 | marketValueStable: string; 46 | marketValueVolatile: string; 47 | marketValuePOL: string; 48 | marketValueCalculated: string; 49 | diff: string; 50 | result: boolean; 51 | output: string; // Markdown 52 | }; 53 | ohmSupply?: { 54 | base: string; 55 | branch: string; 56 | diff: string; 57 | result: boolean; 58 | output: string; // Markdown 59 | }; 60 | /** 61 | * Output for the entire comparison step, in Markdown format. 62 | * 63 | * This will be included in a GitHub comment. 64 | */ 65 | output?: string; 66 | }; 67 | }; 68 | 69 | export const readComparisonFile = (filePath: string): ComparisonResults => { 70 | console.info(`Reading comparison file from ${filePath}`); 71 | 72 | // Silently create the data structure if the file doesn't exist 73 | if (!existsSync(filePath)) { 74 | return { 75 | branches: {}, 76 | records: { 77 | tokenRecords: {}, 78 | tokenSupplies: {}, 79 | }, 80 | results: {}, 81 | }; 82 | } 83 | 84 | return JSON.parse(readFileSync(filePath, "utf8")); 85 | }; 86 | 87 | export const writeComparisonFile = (results: ComparisonResults, filePath: string): void => { 88 | // Create the parent folders, if needed 89 | mkdirSync(dirname(filePath), { recursive: true }); 90 | 91 | // Write the file 92 | writeFileSync(filePath, JSON.stringify(results, null, 2)); 93 | console.info(`Wrote comparison results to ${filePath}`); 94 | }; 95 | -------------------------------------------------------------------------------- /bin/subgraph/src/subgraphs/shared/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseNetworkHandler } from "../../networkHandler"; 2 | import { getLatestBlock, getTestDate, getTokenRecords, getTokenSupplies } from "../../subgraph"; 3 | import { 4 | combineOutput, 5 | compareLiquidBackingRecords, 6 | compareMarketValueRecords, 7 | compareOHMSupplyRecords, 8 | doMarketValueCheck, 9 | } from "./compare"; 10 | import { readComparisonFile, writeComparisonFile } from "./results"; 11 | 12 | export default class EthereumHandler extends BaseNetworkHandler { 13 | async doLatestDate(): Promise { 14 | const comparisonFile = readComparisonFile(this.outputPath); 15 | 16 | const latestDate = await getTestDate(this.subgraphId); 17 | comparisonFile.latestDate = latestDate; 18 | 19 | writeComparisonFile(comparisonFile, this.outputPath); 20 | } 21 | 22 | async doQuery(): Promise { 23 | const comparisonFile = readComparisonFile(this.outputPath); 24 | const branchInfo = { 25 | subgraphId: "", 26 | blockNumber: "", 27 | }; 28 | 29 | // Fetch the latest block for each branch 30 | const latestBlock = await getLatestBlock(this.subgraphId, comparisonFile.latestDate); 31 | branchInfo.blockNumber = latestBlock; 32 | 33 | const tokenRecords = await getTokenRecords(this.subgraphId, latestBlock); 34 | const tokenSupplies = await getTokenSupplies(this.subgraphId, latestBlock); 35 | 36 | // Update the comparison results and write 37 | branchInfo.subgraphId = this.subgraphId; 38 | 39 | comparisonFile.records.tokenRecords[this.branch] = tokenRecords; 40 | comparisonFile.records.tokenSupplies[this.branch] = tokenSupplies; 41 | comparisonFile.branches[this.branch] = branchInfo; 42 | 43 | writeComparisonFile(comparisonFile, this.outputPath); 44 | } 45 | 46 | async doComparison(): Promise { 47 | const comparisonFile = readComparisonFile(this.outputPath); 48 | 49 | // Read TokenRecord files, parse into JSON 50 | const baseRecords = comparisonFile.records.tokenRecords.base; 51 | const branchRecords = comparisonFile.records.tokenRecords.branch; 52 | 53 | const tokenSuppliesBase = comparisonFile.records.tokenSupplies.base; 54 | const tokenSuppliesBranch = comparisonFile.records.tokenSupplies.branch; 55 | 56 | compareMarketValueRecords(baseRecords, branchRecords, comparisonFile); 57 | compareLiquidBackingRecords(baseRecords, branchRecords, comparisonFile); 58 | compareOHMSupplyRecords(tokenSuppliesBase, tokenSuppliesBranch, comparisonFile); 59 | 60 | doMarketValueCheck(branchRecords, comparisonFile); 61 | 62 | combineOutput(this.network, comparisonFile); 63 | 64 | writeComparisonFile(comparisonFile, this.outputPath); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/protocolMetrics/Rebase.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { log } from "matchstick-as"; 3 | 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { OlympusStakingV1 } from "../../generated/ProtocolMetrics/OlympusStakingV1"; 6 | import { OlympusStakingV2 } from "../../generated/ProtocolMetrics/OlympusStakingV2"; 7 | import { OlympusStakingV3 } from "../../generated/ProtocolMetrics/OlympusStakingV3"; 8 | import { 9 | STAKING_CONTRACT_V1, 10 | STAKING_CONTRACT_V2, 11 | STAKING_CONTRACT_V2_BLOCK, 12 | STAKING_CONTRACT_V3, 13 | STAKING_CONTRACT_V3_BLOCK, 14 | } from "../utils/Constants"; 15 | 16 | export function getNextOHMRebase(blockNumber: BigInt): BigDecimal { 17 | let next_distribution = BigDecimal.fromString("0"); 18 | 19 | const staking_contract_v1 = OlympusStakingV1.bind(Address.fromString(STAKING_CONTRACT_V1)); 20 | const response = staking_contract_v1.try_ohmToDistributeNextEpoch(); 21 | if (response.reverted == false) { 22 | next_distribution = toDecimal(response.value, 9); 23 | log.debug("next_distribution v1 {}", [next_distribution.toString()]); 24 | } else { 25 | log.debug("reverted staking_contract_v1", []); 26 | } 27 | 28 | if (blockNumber.gt(BigInt.fromString(STAKING_CONTRACT_V2_BLOCK))) { 29 | const staking_contract_v2 = OlympusStakingV2.bind(Address.fromString(STAKING_CONTRACT_V2)); 30 | const distribution_v2 = toDecimal(staking_contract_v2.epoch().value3, 9); 31 | log.debug("next_distribution v2 {}", [distribution_v2.toString()]); 32 | next_distribution = next_distribution.plus(distribution_v2); 33 | } 34 | 35 | if (blockNumber.gt(BigInt.fromString(STAKING_CONTRACT_V3_BLOCK))) { 36 | const staking_contract_v3 = OlympusStakingV3.bind(Address.fromString(STAKING_CONTRACT_V3)); 37 | const distribution_v3 = toDecimal(staking_contract_v3.epoch().value3, 9); 38 | log.debug("next_distribution v3 {}", [distribution_v3.toString()]); 39 | next_distribution = next_distribution.plus(distribution_v3); 40 | } 41 | 42 | log.debug("next_distribution total {}", [next_distribution.toString()]); 43 | 44 | return next_distribution; 45 | } 46 | 47 | export function getAPY_Rebase(sOHM: BigDecimal, distributedOHM: BigDecimal): BigDecimal[] { 48 | const nextEpochRebase = distributedOHM.div(sOHM).times(BigDecimal.fromString("100")); 49 | 50 | const nextEpochRebase_number = parseFloat(nextEpochRebase.toString()); 51 | const currentAPY = (Math.pow(nextEpochRebase_number / 100 + 1, 365 * 3) - 1) * 100; 52 | 53 | const currentAPYdecimal = BigDecimal.fromString(currentAPY.toString()); 54 | 55 | log.debug("next_rebase {}", [nextEpochRebase.toString()]); 56 | log.debug("current_apy total {}", [currentAPYdecimal.toString()]); 57 | 58 | return [currentAPYdecimal, nextEpochRebase]; 59 | } 60 | -------------------------------------------------------------------------------- /subgraphs/shared/tests/price/PriceHandlerERC4626.test.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; 2 | import { assert, createMockedFunction, describe, test } from "matchstick-as"; 3 | 4 | import { ContractNameLookup } from "../../src/contracts/ContractLookup"; 5 | import { PriceLookup, PriceLookupResult } from "../../src/price/PriceHandler"; 6 | import { PriceHandlerERC4626 } from "../../src/price/PriceHandlerERC4626"; 7 | import { toBigInt, toDecimal } from "../../src/utils/Decimals"; 8 | 9 | const ERC4626_SUSDS = "0x808507121B80c02388fAd1472645B5fb38389452".toLowerCase(); 10 | const ERC20_USDS = "0x639A647f9770d913b564BF333464C3CFD9FD292e".toLowerCase(); 11 | const BLOCK = BigInt.fromString("1"); 12 | const SUSDS_ASSETS_TO_SHARES = toDecimal(BigInt.fromString("1040000000000000000"), 18); 13 | 14 | const mockERC4626Token = ( 15 | tokenAddress: string, 16 | underlyingTokenAddress: string, 17 | assetsToShares: BigDecimal, 18 | decimals: u8, 19 | ): void => { 20 | const contractAddress = Address.fromString(tokenAddress); 21 | 22 | // Underlying token address 23 | createMockedFunction(contractAddress, "asset", "asset():(address)").returns([ethereum.Value.fromAddress(Address.fromString(underlyingTokenAddress))]); 24 | 25 | // Decimals 26 | createMockedFunction(contractAddress, "decimals", "decimals():(uint8)").returns([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(decimals))]); 27 | 28 | // Assets to shares 29 | createMockedFunction(contractAddress, "convertToAssets", "convertToAssets(uint256):(uint256)"). 30 | withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10).pow(decimals))]). 31 | returns([ethereum.Value.fromUnsignedBigInt(toBigInt(assetsToShares, decimals))]); 32 | }; 33 | 34 | const mockERC4626Tokens = (): void => { 35 | mockERC4626Token(ERC4626_SUSDS, ERC20_USDS, SUSDS_ASSETS_TO_SHARES, 18); 36 | }; 37 | 38 | describe("getPrice", () => { 39 | test("returns the price of the vault token", () => { 40 | const priceLookup: PriceLookup = (_tokenAddress: string, _block: BigInt): PriceLookupResult => { 41 | return { 42 | liquidity: BigDecimal.fromString("1"), 43 | price: BigDecimal.fromString("1"), 44 | }; 45 | }; 46 | 47 | const contractLookup: ContractNameLookup = (_tokenAddress: string): string => "sUSDS"; 48 | 49 | // Mock the values 50 | mockERC4626Tokens(); 51 | 52 | const handler = new PriceHandlerERC4626(ERC4626_SUSDS, ERC20_USDS, contractLookup); 53 | 54 | // Should return the price of the vault token 55 | const priceResult = handler.getPrice(ERC4626_SUSDS, priceLookup, BLOCK); 56 | assert.stringEquals( 57 | SUSDS_ASSETS_TO_SHARES.toString(), 58 | priceResult ? priceResult.price.toString() : "", 59 | ); 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /subgraphs/ethereum/abis/BondingCalculator.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_OHM", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [], 15 | "name": "OHM", 16 | "outputs": [ 17 | { 18 | "internalType": "address", 19 | "name": "", 20 | "type": "address" 21 | } 22 | ], 23 | "stateMutability": "view", 24 | "type": "function" 25 | }, 26 | { 27 | "inputs": [ 28 | { 29 | "internalType": "address", 30 | "name": "_pair", 31 | "type": "address" 32 | } 33 | ], 34 | "name": "getKValue", 35 | "outputs": [ 36 | { 37 | "internalType": "uint256", 38 | "name": "k_", 39 | "type": "uint256" 40 | } 41 | ], 42 | "stateMutability": "view", 43 | "type": "function" 44 | }, 45 | { 46 | "inputs": [ 47 | { 48 | "internalType": "address", 49 | "name": "_pair", 50 | "type": "address" 51 | } 52 | ], 53 | "name": "getTotalValue", 54 | "outputs": [ 55 | { 56 | "internalType": "uint256", 57 | "name": "_value", 58 | "type": "uint256" 59 | } 60 | ], 61 | "stateMutability": "view", 62 | "type": "function" 63 | }, 64 | { 65 | "inputs": [ 66 | { 67 | "internalType": "address", 68 | "name": "_pair", 69 | "type": "address" 70 | } 71 | ], 72 | "name": "markdown", 73 | "outputs": [ 74 | { 75 | "internalType": "uint256", 76 | "name": "", 77 | "type": "uint256" 78 | } 79 | ], 80 | "stateMutability": "view", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [ 85 | { 86 | "internalType": "address", 87 | "name": "_pair", 88 | "type": "address" 89 | }, 90 | { 91 | "internalType": "uint256", 92 | "name": "amount_", 93 | "type": "uint256" 94 | } 95 | ], 96 | "name": "valuation", 97 | "outputs": [ 98 | { 99 | "internalType": "uint256", 100 | "name": "_value", 101 | "type": "uint256" 102 | } 103 | ], 104 | "stateMutability": "view", 105 | "type": "function" 106 | } 107 | ] -------------------------------------------------------------------------------- /subgraphs/berachain/src/treasury/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { getERC20 } from "../../../shared/src/contracts/ERC20"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getNativeTokenBalances } from "../../../shared/src/utils/TokenNative"; 7 | import { getTokensInCategory } from "../../../shared/src/utils/TokenRecordHelper"; 8 | import { BLOCKCHAIN, ERC20_TOKENS_BERACHAIN, getWalletAddressesForContract, NATIVE_BERA } from "../contracts/Constants"; 9 | import { getContractName, getERC20TokenRecordsFromWallets } from "../contracts/Contracts"; 10 | import { getPrice } from "../price/PriceLookup"; 11 | 12 | /** 13 | * Returns the token records for a given token. This includes: 14 | * - Wallets 15 | * - Allocators 16 | * 17 | * @param contractAddress the address of the ERC20 contract 18 | * @param blockNumber the current block 19 | * @returns TokenRecord array 20 | */ 21 | function getTokenBalance( 22 | timestamp: BigInt, 23 | contractAddress: string, 24 | blockNumber: BigInt, 25 | ): TokenRecord[] { 26 | const contractName = getContractName(contractAddress); 27 | log.info("getTokenBalance: Calculating balance for {} ({}) at block number {}", [ 28 | contractName, 29 | contractAddress, 30 | blockNumber.toString(), 31 | ]); 32 | const records: TokenRecord[] = []; 33 | const contract = getERC20(contractAddress, blockNumber); 34 | if (!contract) { 35 | log.info("getTokenBalance: Skipping ERC20 contract {} that returned empty at block {}", [ 36 | getContractName(contractAddress), 37 | blockNumber.toString(), 38 | ]); 39 | return records; 40 | } 41 | 42 | const rate = getPrice(contractAddress, blockNumber); 43 | 44 | // Standard ERC20 45 | pushTokenRecordArray( 46 | records, 47 | getERC20TokenRecordsFromWallets(timestamp, contractAddress, contract, rate, blockNumber), 48 | ); 49 | 50 | return records; 51 | } 52 | 53 | /** 54 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 55 | * 56 | * @param timestamp 57 | * @param blockNumber the current block 58 | * @returns TokenRecord array 59 | */ 60 | export function getTokenBalances( 61 | timestamp: BigInt, 62 | category: string, 63 | blockNumber: BigInt, 64 | ): TokenRecord[] { 65 | const records: TokenRecord[] = []; 66 | 67 | const categoryTokens = getTokensInCategory(category, ERC20_TOKENS_BERACHAIN); 68 | for (let i = 0; i < categoryTokens.length; i++) { 69 | const tokenAddress = categoryTokens[i].getAddress(); 70 | 71 | if (tokenAddress.toLowerCase() == NATIVE_BERA.toLowerCase()) { 72 | pushTokenRecordArray(records, getNativeTokenBalances(timestamp, blockNumber, BLOCKCHAIN, getWalletAddressesForContract(NATIVE_BERA), getPrice, getContractName)); 73 | } else { 74 | pushTokenRecordArray(records, getTokenBalance(timestamp, tokenAddress, blockNumber)); 75 | } 76 | } 77 | 78 | return records; 79 | } 80 | -------------------------------------------------------------------------------- /subgraphs/polygon/src/price/PriceLookup.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { PriceHandler, PriceLookupResult } from "../../../shared/src/price/PriceHandler"; 4 | import { PriceHandlerCustomMapping } from "../../../shared/src/price/PriceHandlerCustomMapping"; 5 | import { PriceHandlerStablecoin } from "../../../shared/src/price/PriceHandlerStablecoin"; 6 | import { PriceHandlerUniswapV2 } from "../../../shared/src/price/PriceHandlerUniswapV2"; 7 | import { getUSDRate } from "../../../shared/src/price/PriceRouter"; 8 | import { 9 | ERC20_DAI, 10 | ERC20_FRAX, 11 | ERC20_GOHM, 12 | ERC20_KLIMA, 13 | ERC20_KLIMA_STAKED, 14 | ERC20_SYN, 15 | ERC20_USDC, 16 | ERC20_WETH, 17 | LP_UNISWAP_V2_KLIMA_USDC, 18 | LP_UNISWAP_V2_SYN_WETH, 19 | LP_UNISWAP_V2_USDC_WETH, 20 | LP_UNISWAP_V2_WETH_GOHM, 21 | } from "../contracts/Constants"; 22 | import { getContractName } from "../contracts/Contracts"; 23 | 24 | export const HANDLERS: PriceHandler[] = [ 25 | new PriceHandlerCustomMapping(ERC20_KLIMA, [ERC20_KLIMA_STAKED], getContractName), 26 | new PriceHandlerStablecoin([ERC20_DAI, ERC20_FRAX, ERC20_USDC], getContractName), 27 | new PriceHandlerUniswapV2([ERC20_GOHM, ERC20_WETH], LP_UNISWAP_V2_WETH_GOHM, getContractName), 28 | new PriceHandlerUniswapV2([ERC20_KLIMA, ERC20_USDC], LP_UNISWAP_V2_KLIMA_USDC, getContractName), 29 | new PriceHandlerUniswapV2([ERC20_SYN, ERC20_WETH], LP_UNISWAP_V2_SYN_WETH, getContractName), 30 | new PriceHandlerUniswapV2([ERC20_USDC, ERC20_WETH], LP_UNISWAP_V2_USDC_WETH, getContractName), 31 | ]; 32 | 33 | /** 34 | * Internal function to determine the price, using the shared price functions. 35 | * 36 | * It simply injects {HANDLERS}, so that {getUSDRate} can operate. 37 | * 38 | * @param tokenAddress 39 | * @param block 40 | * @param currentPool 41 | * @returns 42 | */ 43 | export function getPriceRecursive( 44 | tokenAddress: string, 45 | block: BigInt, 46 | currentPool: string | null, 47 | ): PriceLookupResult | null { 48 | log.debug("Determining price for {} ({}) and current pool id {}", [getContractName(tokenAddress), tokenAddress, currentPool ? currentPool : ""]); 49 | return getUSDRate(tokenAddress, HANDLERS, getPriceRecursive, block, currentPool); 50 | } 51 | 52 | /** 53 | * External-facing function that determines the price of {tokenAddress}. 54 | * 55 | * @param tokenAddress 56 | * @param block 57 | * @returns BigDecimal 58 | * @throws Error if a price cannot be found, so that the subgraph indexing fails quickly 59 | */ 60 | export function getPrice(tokenAddress: string, block: BigInt): BigDecimal { 61 | const priceResult = getPriceRecursive(tokenAddress, block, null); 62 | 63 | if (priceResult === null) { 64 | log.warning("Unable to determine price for token {} ({}) at block {}", [getContractName(tokenAddress), tokenAddress, block.toString()]); 65 | return BigDecimal.zero(); 66 | } 67 | 68 | log.debug("Price for {} ({}) at block {} was: {}", [getContractName(tokenAddress), tokenAddress, block.toString(), priceResult.price.toString()]); 69 | return priceResult.price; 70 | } 71 | -------------------------------------------------------------------------------- /subgraphs/berachain/src/contracts/LiquidityConstants.ts: -------------------------------------------------------------------------------- 1 | import { PriceHandler } from "../../../shared/src/price/PriceHandler"; 2 | import { PriceHandlerBalancer } from "../../../shared/src/price/PriceHandlerBalancer"; 3 | import { PriceHandlerRemapping } from "../../../shared/src/price/PriceHandlerRemapping"; 4 | import { PriceHandlerUniswapV3Quoter } from "../../../shared/src/price/PriceHandlerUniswapV3Quoter"; 5 | import { PriceHandlerKodiakIsland } from "../price/PriceHandlerKodiakIsland"; 6 | import { BERADROME_KODIAK_OHM_HONEY_REWARD_VAULT_V1, BERADROME_KODIAK_OHM_HONEY_REWARD_VAULT_V2, BERAHUB_KODIAK_OHM_HONEY_REWARD_VAULT, BEX_VAULT, ERC20_HONEY, ERC20_IBERA, ERC20_IBGT, ERC20_LBGT, ERC20_OHM, ERC20_WBERA, INFRARED_KODIAK_OHM_HONEY_VAULT, LP_BERADROME_KODIAK_OHM_HONEY, LP_BEX_LBGT_WBERA_ID, LP_KODIAK_IBERA_WBERA_500, LP_KODIAK_IBERA_WBERA_3000, LP_KODIAK_IBGT_WBERA, LP_KODIAK_OHM_HONEY, LP_UNISWAP_V3_WBERA_HONEY, NATIVE_BERA } from "./Constants"; 7 | import { getContractName } from "./Contracts"; 8 | 9 | export const KODIAK_QUOTER = "0x644C8D6E501f7C994B74F5ceA96abe65d0BA662B".toLowerCase(); 10 | 11 | // Owned liquidity 12 | const kodiakOhmHoney = new PriceHandlerKodiakIsland([ERC20_HONEY, ERC20_OHM], KODIAK_QUOTER, LP_KODIAK_OHM_HONEY, null, null, getContractName); 13 | const beradromeKodiakOhmHoneyV1 = new PriceHandlerKodiakIsland([ERC20_HONEY, ERC20_OHM], KODIAK_QUOTER, LP_KODIAK_OHM_HONEY, BERADROME_KODIAK_OHM_HONEY_REWARD_VAULT_V1, LP_BERADROME_KODIAK_OHM_HONEY, getContractName); 14 | const beradromeKodiakOhmHoneyV2 = new PriceHandlerKodiakIsland([ERC20_HONEY, ERC20_OHM], KODIAK_QUOTER, LP_KODIAK_OHM_HONEY, BERADROME_KODIAK_OHM_HONEY_REWARD_VAULT_V2, LP_BERADROME_KODIAK_OHM_HONEY, getContractName); 15 | const infraredKodiakOhmHoney = new PriceHandlerKodiakIsland([ERC20_HONEY, ERC20_OHM], KODIAK_QUOTER, LP_KODIAK_OHM_HONEY, INFRARED_KODIAK_OHM_HONEY_VAULT, INFRARED_KODIAK_OHM_HONEY_VAULT, getContractName); 16 | const beraHubKodiakOhmHoney = new PriceHandlerKodiakIsland([ERC20_HONEY, ERC20_OHM], KODIAK_QUOTER, LP_KODIAK_OHM_HONEY, BERAHUB_KODIAK_OHM_HONEY_REWARD_VAULT, BERAHUB_KODIAK_OHM_HONEY_REWARD_VAULT, getContractName); 17 | export const OWNED_LIQUIDITY_HANDLERS = [kodiakOhmHoney, beradromeKodiakOhmHoneyV1, beradromeKodiakOhmHoneyV2, infraredKodiakOhmHoney, beraHubKodiakOhmHoney]; 18 | 19 | // Price handlers 20 | // Price lookup is enabled for these tokens 21 | export const PRICE_HANDLERS: PriceHandler[] = [ 22 | new PriceHandlerUniswapV3Quoter([ERC20_HONEY, ERC20_WBERA], KODIAK_QUOTER, LP_UNISWAP_V3_WBERA_HONEY, getContractName), 23 | new PriceHandlerUniswapV3Quoter([ERC20_IBERA, ERC20_WBERA], KODIAK_QUOTER, LP_KODIAK_IBERA_WBERA_3000, getContractName), 24 | new PriceHandlerUniswapV3Quoter([ERC20_IBERA, ERC20_WBERA], KODIAK_QUOTER, LP_KODIAK_IBERA_WBERA_500, getContractName), 25 | kodiakOhmHoney, 26 | beradromeKodiakOhmHoneyV1, 27 | beradromeKodiakOhmHoneyV2, 28 | beraHubKodiakOhmHoney, 29 | new PriceHandlerUniswapV3Quoter([ERC20_IBGT, ERC20_WBERA], KODIAK_QUOTER, LP_KODIAK_IBGT_WBERA, getContractName), 30 | new PriceHandlerRemapping(NATIVE_BERA, ERC20_WBERA, getContractName), 31 | new PriceHandlerBalancer([ERC20_LBGT, ERC20_WBERA], BEX_VAULT, LP_BEX_LBGT_WBERA_ID, getContractName), 32 | ]; 33 | -------------------------------------------------------------------------------- /subgraphs/base/src/price/PriceLookup.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { PriceHandler, PriceLookupResult } from "../../../shared/src/price/PriceHandler"; 4 | import { PriceHandlerUniswapV2 } from "../../../shared/src/price/PriceHandlerUniswapV2"; 5 | import { PriceHandlerUniswapV3 } from "../../../shared/src/price/PriceHandlerUniswapV3"; 6 | import { getUSDRate } from "../../../shared/src/price/PriceRouter"; 7 | import { 8 | ERC20_OHM, 9 | ERC20_USDC, 10 | ERC20_WETH, 11 | LP_UNISWAP_V2_OHM_WETH, 12 | LP_UNISWAP_V3_OHM_USDC, 13 | UNISWAP_V3_POSITION_MANAGER 14 | } from "../contracts/Constants"; 15 | import { getContractName } from "../contracts/Contracts"; 16 | import { getBaseTokenRate, isBaseToken } from "./PriceBase"; 17 | 18 | export const PRICE_HANDLERS: PriceHandler[] = [ 19 | new PriceHandlerUniswapV2([ERC20_OHM, ERC20_WETH], LP_UNISWAP_V2_OHM_WETH, getContractName), 20 | new PriceHandlerUniswapV3([ERC20_OHM, ERC20_USDC], LP_UNISWAP_V3_OHM_USDC, UNISWAP_V3_POSITION_MANAGER, getContractName), 21 | ]; 22 | 23 | /** 24 | * Internal function to determine the price, using the shared price functions. 25 | * 26 | * It simply injects {HANDLERS}, so that {getUSDRate} can operate. 27 | * 28 | * @param tokenAddress 29 | * @param block 30 | * @param currentPool 31 | * @returns 32 | */ 33 | export function getPriceRecursive( 34 | tokenAddress: string, 35 | block: BigInt, 36 | currentPool: string | null, 37 | ): PriceLookupResult | null { 38 | const FUNC = "getPriceRecursive"; 39 | 40 | /** 41 | * Check for a base token in this function, instead of getPrice, so that recursive checks can use price feeds. 42 | */ 43 | if (isBaseToken(tokenAddress)) { 44 | const usdRate = getBaseTokenRate(Address.fromString(tokenAddress), block); 45 | return { 46 | price: usdRate, 47 | liquidity: new BigDecimal(BigInt.fromU64(U64.MAX_VALUE)) 48 | } 49 | } 50 | 51 | log.debug("{}: Determining price for {} ({}) and current pool id {}", [FUNC, getContractName(tokenAddress), tokenAddress, currentPool ? currentPool : ""]); 52 | return getUSDRate(tokenAddress, PRICE_HANDLERS, getPriceRecursive, block, currentPool); 53 | } 54 | 55 | /** 56 | * External-facing function that determines the price of {tokenAddress}. 57 | * 58 | * @param tokenAddress 59 | * @param block 60 | * @returns BigDecimal 61 | * @throws Error if a price cannot be found, so that the subgraph indexing fails quickly 62 | */ 63 | export function getPrice(tokenAddress: string, block: BigInt): BigDecimal { 64 | const FUNC = "getPrice"; 65 | log.debug(`${FUNC}: Determining price for ${getContractName(tokenAddress)} (${tokenAddress}) at block ${block.toString()}`, []); 66 | 67 | const priceResult = getPriceRecursive(tokenAddress, block, null); 68 | 69 | if (priceResult === null) { 70 | log.warning("{}: Unable to determine price for token {} ({}) at block {}", [FUNC, getContractName(tokenAddress), tokenAddress, block.toString()]); 71 | return BigDecimal.zero(); 72 | } 73 | 74 | log.debug("{}: Price for {} ({}) at block {} was: {}", [FUNC, getContractName(tokenAddress), tokenAddress, block.toString(), priceResult.price.toString()]); 75 | return priceResult.price; 76 | } 77 | -------------------------------------------------------------------------------- /bin/subgraph/src/subgraphs/ethereum/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseNetworkHandler } from "../../networkHandler"; 2 | import { getLatestBlock, getOhmPrice, getTestDate, getTokenRecords, getTokenSupplies } from "../../subgraph"; 3 | import { 4 | combineOutput, 5 | compareBackedSupplyRecords, 6 | compareCirculatingSupplyRecords, 7 | compareFloatingSupplyRecords, 8 | compareLiquidBackingRecords, 9 | compareMarketValueRecords, 10 | doLiquidBackingCheck, 11 | doMarketValueCheck, 12 | } from "./compare"; 13 | import { readComparisonFile, writeComparisonFile } from "./results"; 14 | 15 | export default class EthereumHandler extends BaseNetworkHandler { 16 | async doLatestDate(): Promise { 17 | const comparisonFile = readComparisonFile(this.outputPath); 18 | 19 | const latestDate = await getTestDate(this.subgraphId); 20 | comparisonFile.latestDate = latestDate; 21 | 22 | writeComparisonFile(comparisonFile, this.outputPath); 23 | } 24 | 25 | async doQuery(): Promise { 26 | const comparisonFile = readComparisonFile(this.outputPath); 27 | const branchInfo = { 28 | subgraphId: "", 29 | blockNumber: "", 30 | }; 31 | 32 | // Fetch the latest block for each branch 33 | const latestBlock = await getLatestBlock(this.subgraphId, comparisonFile.latestDate); 34 | branchInfo.blockNumber = latestBlock; 35 | 36 | const tokenRecords = await getTokenRecords(this.subgraphId, latestBlock); 37 | const tokenSupplies = await getTokenSupplies(this.subgraphId, latestBlock); 38 | 39 | // Update the comparison results and write 40 | branchInfo.subgraphId = this.subgraphId; 41 | 42 | comparisonFile.records.tokenRecords[this.branch] = tokenRecords; 43 | comparisonFile.records.tokenSupplies[this.branch] = tokenSupplies; 44 | comparisonFile.branches[this.branch] = branchInfo; 45 | 46 | writeComparisonFile(comparisonFile, this.outputPath); 47 | } 48 | 49 | async doComparison(): Promise { 50 | const comparisonFile = readComparisonFile(this.outputPath); 51 | 52 | // Read TokenRecord files, parse into JSON 53 | const tokenRecordsBase = comparisonFile.records.tokenRecords.base; 54 | const tokenRecordsBranch = comparisonFile.records.tokenRecords.branch; 55 | 56 | const tokenSuppliesBase = comparisonFile.records.tokenSupplies.base; 57 | const tokenSuppliesBranch = comparisonFile.records.tokenSupplies.branch; 58 | 59 | compareMarketValueRecords(tokenRecordsBase, tokenRecordsBranch, comparisonFile); 60 | compareLiquidBackingRecords(tokenRecordsBase, tokenRecordsBranch, comparisonFile); 61 | 62 | // Get OHM price 63 | const subgraphId = comparisonFile.branches.branch.subgraphId; 64 | const block = comparisonFile.branches.branch.blockNumber; 65 | const ohmPrice = await getOhmPrice(subgraphId, block); 66 | 67 | doLiquidBackingCheck(tokenRecordsBranch, tokenSuppliesBranch, ohmPrice, comparisonFile); 68 | doMarketValueCheck(tokenRecordsBranch, comparisonFile); 69 | 70 | compareCirculatingSupplyRecords(tokenSuppliesBase, tokenSuppliesBranch, comparisonFile); 71 | compareFloatingSupplyRecords(tokenSuppliesBase, tokenSuppliesBranch, comparisonFile); 72 | compareBackedSupplyRecords(tokenSuppliesBase, tokenSuppliesBranch, comparisonFile); 73 | 74 | combineOutput(this.network, comparisonFile); 75 | 76 | writeComparisonFile(comparisonFile, this.outputPath); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /subgraphs/fantom/src/price/PriceLookup.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20_WETH } from "../../../arbitrum/src/contracts/Constants"; 4 | import { PriceHandler, PriceLookupResult } from "../../../shared/src/price/PriceHandler"; 5 | import { PriceHandlerStablecoin } from "../../../shared/src/price/PriceHandlerStablecoin"; 6 | import { PriceHandlerUniswapV2 } from "../../../shared/src/price/PriceHandlerUniswapV2"; 7 | import { getUSDRate } from "../../../shared/src/price/PriceRouter"; 8 | import { 9 | ERC20_BEETS, 10 | ERC20_BOO, 11 | ERC20_DAI, 12 | ERC20_DEI, 13 | ERC20_FRAX, 14 | ERC20_GOHM, 15 | ERC20_LQDR, 16 | ERC20_OXD, 17 | ERC20_USDC, 18 | ERC20_WFTM, 19 | LP_UNISWAP_V2_BOO_WFTM, 20 | LP_UNISWAP_V2_LQDR_WFTM, 21 | LP_UNISWAP_V2_WFTM_BEETS, 22 | LP_UNISWAP_V2_WFTM_ETH, 23 | LP_UNISWAP_V2_WFTM_GOHM, 24 | LP_UNISWAP_V2_WFTM_OXD, 25 | LP_UNISWAP_V2_WFTM_USDC, 26 | } from "../contracts/Constants"; 27 | import { getContractName } from "../contracts/Contracts"; 28 | 29 | export const HANDLERS: PriceHandler[] = [ 30 | new PriceHandlerStablecoin([ERC20_DAI, ERC20_DEI, ERC20_FRAX, ERC20_USDC], getContractName), 31 | new PriceHandlerUniswapV2([ERC20_BOO, ERC20_WFTM], LP_UNISWAP_V2_BOO_WFTM, getContractName), 32 | new PriceHandlerUniswapV2([ERC20_GOHM, ERC20_WFTM], LP_UNISWAP_V2_WFTM_GOHM, getContractName), 33 | new PriceHandlerUniswapV2([ERC20_LQDR, ERC20_WFTM], LP_UNISWAP_V2_LQDR_WFTM, getContractName), 34 | new PriceHandlerUniswapV2([ERC20_USDC, ERC20_WFTM], LP_UNISWAP_V2_WFTM_USDC, getContractName), 35 | new PriceHandlerUniswapV2([ERC20_WFTM, ERC20_BEETS], LP_UNISWAP_V2_WFTM_BEETS, getContractName), 36 | new PriceHandlerUniswapV2([ERC20_WFTM, ERC20_OXD], LP_UNISWAP_V2_WFTM_OXD, getContractName), 37 | new PriceHandlerUniswapV2([ERC20_WFTM, ERC20_WETH], LP_UNISWAP_V2_WFTM_ETH, getContractName), 38 | ]; 39 | 40 | /** 41 | * Internal function to determine the price, using the shared price functions. 42 | * 43 | * It simply injects {HANDLERS}, so that {getUSDRate} can operate. 44 | * 45 | * @param tokenAddress 46 | * @param block 47 | * @param currentPool 48 | * @returns 49 | */ 50 | export function getPriceRecursive( 51 | tokenAddress: string, 52 | block: BigInt, 53 | currentPool: string | null, 54 | ): PriceLookupResult | null { 55 | log.debug("Determining price for {} ({}) and current pool id {}", [getContractName(tokenAddress), tokenAddress, currentPool ? currentPool : ""]); 56 | return getUSDRate(tokenAddress, HANDLERS, getPriceRecursive, block, currentPool); 57 | } 58 | 59 | /** 60 | * External-facing function that determines the price of {tokenAddress}. 61 | * 62 | * @param tokenAddress 63 | * @param block 64 | * @returns BigDecimal 65 | * @throws Error if a price cannot be found, so that the subgraph indexing fails quickly 66 | */ 67 | export function getPrice(tokenAddress: string, block: BigInt): BigDecimal { 68 | const priceResult = getPriceRecursive(tokenAddress, block, null); 69 | 70 | if (priceResult === null) { 71 | log.warning("Unable to determine price for token {} ({}) at block {}", [getContractName(tokenAddress), tokenAddress, block.toString()]); 72 | return BigDecimal.zero(); 73 | } 74 | 75 | log.debug("Price for {} ({}) at block {} was: {}", [getContractName(tokenAddress), tokenAddress, block.toString(), priceResult.price.toString()]); 76 | return priceResult.price; 77 | } 78 | -------------------------------------------------------------------------------- /bin/subgraph/src/subgraphs/ethereum/results.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; 2 | import { dirname } from "path"; 3 | 4 | import { TokenRecord, TokenSupply } from "../../subgraph"; 5 | 6 | export type ComparisonResults = { 7 | latestDate?: string; 8 | branches: { 9 | base?: { 10 | blockNumber?: string; 11 | subgraphId: string; 12 | }; 13 | branch?: { 14 | blockNumber?: string; 15 | subgraphId: string; 16 | }; 17 | }; 18 | records: { 19 | tokenRecords: { 20 | base?: TokenRecord[]; 21 | branch?: TokenRecord[]; 22 | }; 23 | tokenSupplies: { 24 | base?: TokenSupply[]; 25 | branch?: TokenSupply[]; 26 | }; 27 | }; 28 | results: { 29 | marketValue?: { 30 | base: string; 31 | branch: string; 32 | diff: string; 33 | result: boolean; 34 | output: string; // Markdown 35 | }; 36 | liquidBacking?: { 37 | base: string; 38 | branch: string; 39 | diff: string; 40 | result: boolean; 41 | output: string; // Markdown 42 | }; 43 | liquidBackingCheck?: { 44 | marketValue: string; 45 | liquidBacking: string; 46 | ohmInLiquidity: string; 47 | ohmPrice: string; 48 | illiquidAssets: string; 49 | diff: string; 50 | result: boolean; 51 | output: string; // Markdown 52 | }; 53 | marketValueCheck?: { 54 | marketValueTotal: string; 55 | marketValueStable: string; 56 | marketValueVolatile: string; 57 | marketValuePOL: string; 58 | marketValueCalculated: string; 59 | diff: string; 60 | result: boolean; 61 | output: string; // Markdown 62 | }; 63 | circulatingSupply?: { 64 | base: string; 65 | branch: string; 66 | diff: string; 67 | result: boolean; 68 | output: string; // Markdown 69 | }; 70 | floatingSupply?: { 71 | base: string; 72 | branch: string; 73 | diff: string; 74 | result: boolean; 75 | output: string; // Markdown 76 | }; 77 | backedSupply?: { 78 | base: string; 79 | branch: string; 80 | diff: string; 81 | result: boolean; 82 | output: string; // Markdown 83 | }; 84 | /** 85 | * Output for the entire comparison step, in Markdown format. 86 | * 87 | * This will be included in a GitHub comment. 88 | */ 89 | output?: string; 90 | }; 91 | }; 92 | 93 | export const readComparisonFile = (filePath: string): ComparisonResults => { 94 | console.info(`Reading comparison file from ${filePath}`); 95 | 96 | // Silently create the data structure if the file doesn't exist 97 | if (!existsSync(filePath)) { 98 | return { 99 | branches: {}, 100 | records: { 101 | tokenRecords: {}, 102 | tokenSupplies: {}, 103 | }, 104 | results: {}, 105 | }; 106 | } 107 | 108 | return JSON.parse(readFileSync(filePath, "utf8")); 109 | }; 110 | 111 | export const writeComparisonFile = (results: ComparisonResults, filePath: string): void => { 112 | // Create the parent folders, if needed 113 | mkdirSync(dirname(filePath), { recursive: true }); 114 | 115 | // Write the file 116 | writeFileSync(filePath, JSON.stringify(results, null, 2)); 117 | console.info(`Wrote comparison results to ${filePath}`); 118 | }; 119 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/treasury/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { getERC20 } from "../../../shared/src/contracts/ERC20"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getTokensInCategory } from "../../../shared/src/utils/TokenRecordHelper"; 7 | import { ERC20_LUSD, ERC20_TOKENS_ARBITRUM, LUSD_START_BLOCK } from "../contracts/Constants"; 8 | import { getContractName, getERC20TokenRecordsFromWallets } from "../contracts/Contracts"; 9 | import { getStakedBalances as getJonesStakedBalances } from "../contracts/JonesStaking"; 10 | import { getStakedBalances as getTreasureStakedBalances } from "../contracts/TreasureMining"; 11 | import { getPrice } from "../price/PriceLookup"; 12 | 13 | /** 14 | * Returns the token records for a given token. This includes: 15 | * - Wallets 16 | * - Allocators 17 | * 18 | * @param contractAddress the address of the ERC20 contract 19 | * @param blockNumber the current block 20 | * @returns TokenRecord array 21 | */ 22 | function getTokenBalance( 23 | timestamp: BigInt, 24 | contractAddress: string, 25 | blockNumber: BigInt, 26 | ): TokenRecord[] { 27 | const contractName = getContractName(contractAddress); 28 | log.info("getTokenBalance: Calculating balance for {} ({}) at block number {}", [ 29 | contractName, 30 | contractAddress, 31 | blockNumber.toString(), 32 | ]); 33 | const records: TokenRecord[] = []; 34 | const contract = getERC20(contractAddress, blockNumber); 35 | if (!contract) { 36 | log.info("getTokenBalance: Skipping ERC20 contract {} that returned empty at block {}", [ 37 | getContractName(contractAddress), 38 | blockNumber.toString(), 39 | ]); 40 | return records; 41 | } 42 | 43 | // If the token is LUSD and the block number is less that the start block of the Chainlink feed, skip it 44 | if (contractAddress.toLowerCase() == ERC20_LUSD.toLowerCase() 45 | && 46 | blockNumber.lt(BigInt.fromString(LUSD_START_BLOCK))) { 47 | log.info("getTokenBalance: Skipping {} token record at block {}", [getContractName(ERC20_LUSD), blockNumber.toString()]); 48 | return records; 49 | } 50 | 51 | const rate = getPrice(contractAddress, blockNumber); 52 | 53 | // Standard ERC20 54 | pushTokenRecordArray( 55 | records, 56 | getERC20TokenRecordsFromWallets(timestamp, contractAddress, contract, rate, blockNumber), 57 | ); 58 | 59 | // Jones Staking 60 | pushTokenRecordArray(records, getJonesStakedBalances(timestamp, contractAddress, blockNumber)); 61 | 62 | // TreasureDAO Staking 63 | pushTokenRecordArray(records, getTreasureStakedBalances(timestamp, contractAddress, blockNumber)); 64 | 65 | return records; 66 | } 67 | 68 | /** 69 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 70 | * 71 | * @param timestamp 72 | * @param blockNumber the current block 73 | * @returns TokenRecord array 74 | */ 75 | export function getTokenBalances( 76 | timestamp: BigInt, 77 | category: string, 78 | blockNumber: BigInt, 79 | ): TokenRecord[] { 80 | const records: TokenRecord[] = []; 81 | 82 | const categoryTokens = getTokensInCategory(category, ERC20_TOKENS_ARBITRUM); 83 | for (let i = 0; i < categoryTokens.length; i++) { 84 | pushTokenRecordArray(records, getTokenBalance(timestamp, categoryTokens[i].getAddress(), blockNumber)); 85 | } 86 | 87 | return records; 88 | } 89 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/treasury/OwnedLiquidity.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { PriceHandler } from "../../../shared/src/price/PriceHandler"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { 7 | createTokenRecord, 8 | getIsTokenLiquid, 9 | } from "../../../shared/src/utils/TokenRecordHelper"; 10 | import { BLOCKCHAIN, ERC20_TOKENS_ARBITRUM, getProtocolAddresses, OHM_TOKENS } from "../contracts/Constants"; 11 | import { getContractName } from "../contracts/Contracts"; 12 | import { getPriceRecursive, PRICE_HANDLERS } from "../price/PriceLookup"; 13 | 14 | /** 15 | * Returns the token records for a given token. This includes: 16 | * - Wallets 17 | * - Allocators 18 | * 19 | * @param contractAddress the address of the ERC20 contract 20 | * @param block the current block 21 | * @returns TokenRecord array 22 | */ 23 | function getOwnedLiquidityBalance( 24 | timestamp: BigInt, 25 | liquidityHandler: PriceHandler, 26 | block: BigInt, 27 | ): TokenRecord[] { 28 | const contractName = getContractName(liquidityHandler.getId()); 29 | log.info("getOwnedLiquidityBalance: Calculating balance for {} ({}) at block number {}", [ 30 | contractName, 31 | liquidityHandler.getId(), 32 | block.toString(), 33 | ]); 34 | const records: TokenRecord[] = []; 35 | 36 | // Calculate the multiplier 37 | const totalValue = liquidityHandler.getTotalValue([], getPriceRecursive, block); 38 | if (!totalValue || totalValue.equals(BigDecimal.zero())) { 39 | return records; 40 | } 41 | const includedValue = liquidityHandler.getTotalValue(OHM_TOKENS, getPriceRecursive, block); 42 | if (!includedValue) { 43 | return records; 44 | } 45 | const multiplier = includedValue.div(totalValue); 46 | 47 | // Get the unit rate 48 | const unitRate = liquidityHandler.getUnitPrice(getPriceRecursive, block); 49 | if (!unitRate) { 50 | return records; 51 | } 52 | 53 | const allWallets = getProtocolAddresses(); 54 | for (let i = 0; i < allWallets.length; i++) { 55 | const currentWalletAddress = allWallets[i]; 56 | 57 | // Get the balance 58 | const balance = liquidityHandler.getBalance(currentWalletAddress, block); 59 | if (balance.equals(BigDecimal.zero())) { 60 | continue; 61 | } 62 | 63 | // Create record 64 | const record = createTokenRecord( 65 | timestamp, 66 | getContractName(liquidityHandler.getId()), 67 | liquidityHandler.getId(), 68 | getContractName(currentWalletAddress), 69 | currentWalletAddress, 70 | unitRate, 71 | balance, 72 | block, 73 | getIsTokenLiquid(liquidityHandler.getId(), ERC20_TOKENS_ARBITRUM), 74 | ERC20_TOKENS_ARBITRUM, 75 | BLOCKCHAIN, 76 | multiplier, 77 | ); 78 | records.push(record); 79 | } 80 | 81 | return records; 82 | } 83 | 84 | /** 85 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 86 | * 87 | * @param timestamp 88 | * @param blockNumber the current block 89 | * @returns TokenRecord array 90 | */ 91 | export function getOwnedLiquidityBalances(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 92 | const records: TokenRecord[] = []; 93 | 94 | for (let i = 0; i < PRICE_HANDLERS.length; i++) { 95 | pushTokenRecordArray(records, getOwnedLiquidityBalance(timestamp, PRICE_HANDLERS[i], blockNumber)); 96 | } 97 | 98 | return records; 99 | } 100 | -------------------------------------------------------------------------------- /subgraphs/shared/src/price/PriceHandler.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | // Prettier is disabled for this file, as PriceLookup will cause compilation problems after wrapping 4 | // https://github.com/AssemblyScript/assemblyscript/issues/2509 5 | 6 | export class PriceLookupResult { 7 | liquidity: BigDecimal; 8 | price: BigDecimal; 9 | } 10 | 11 | /** 12 | * Base function for doing price lookups. 13 | * 14 | * This should be implemented for each network and include the following: 15 | * - inject the array of PriceHandler objects 16 | * - loop through PriceHandler objects and look up prices 17 | * - skip any handlers with getId() == currentPool 18 | * 19 | * The returned type enables the calling function to choose between the results of different 20 | * price handlers. For example, choosing the result with greater liquidity. 21 | * 22 | * @param tokenAddress 23 | * @param block 24 | * @param currentPool the id of the current pool, using getId() 25 | * @returns PriceLookupResult 26 | */ 27 | export type PriceLookup = (tokenAddress: string, block: BigInt, currentPool: string | null) => PriceLookupResult | null; 28 | 29 | /** 30 | * Defines how to determine the price of particular tokens, by mapping them to 31 | * a liquidity pool. 32 | */ 33 | export interface PriceHandler { 34 | /** 35 | * Returns a unique identifier for the PriceHandler. 36 | */ 37 | getId(): string; 38 | 39 | /** 40 | * @returns true if the liquidity pool exists at the current block 41 | */ 42 | exists(): boolean; 43 | 44 | /** 45 | * @returns true if {tokenAddress} can be handled 46 | */ 47 | matches(tokenAddress: string): boolean; 48 | 49 | /** 50 | * @returns array of token addresses that this handler supports 51 | */ 52 | getTokens(): string[]; 53 | 54 | /** 55 | * Determines the price of {tokenAddress} in USD. 56 | * 57 | * Implementations will support the price lookup of tokens other than {tokenAddress} 58 | * through the {priceLookup} argument. This enables recursion without hard-coding 59 | * a specific file. 60 | * 61 | * @param tokenAddress 62 | * @param priceLookup pass in a function to perform a price lookup for other tokens 63 | * @param block 64 | * @returns PriceLookupResult or null 65 | */ 66 | getPrice(tokenAddress: string, priceLookup: PriceLookup, block: BigInt): PriceLookupResult | null; 67 | 68 | /** 69 | * Returns the total value of the liquidity pool. 70 | * 71 | * @param excludedTokens if specified, the total value will exclude the value of these tokens 72 | * @param priceLookup function to perform price lookups 73 | * @param block 74 | */ 75 | getTotalValue(excludedTokens: string[], priceLookup: PriceLookup, block: BigInt): BigDecimal | null; 76 | 77 | /** 78 | * Returns the unit price of the liquidity pool. 79 | * 80 | * @param priceLookup 81 | * @param block 82 | */ 83 | getUnitPrice(priceLookup: PriceLookup, block: BigInt): BigDecimal | null; 84 | 85 | /** 86 | * Returns the balance of the liquidity pool held by {walletAddress}. 87 | * 88 | * @param walletAddress 89 | * @param block 90 | */ 91 | getBalance(walletAddress: string, block: BigInt): BigDecimal; 92 | 93 | /** 94 | * Returns the balance of the underlying token held by the liquidity pool. 95 | * 96 | * @param tokenAddress 97 | * @param block 98 | */ 99 | getUnderlyingTokenBalance(walletAddress: string, tokenAddress: string, block: BigInt): BigDecimal; 100 | } 101 | -------------------------------------------------------------------------------- /subgraphs/polygon/src/treasury/OwnedLiquidity.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { PriceHandler } from "../../../shared/src/price/PriceHandler"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { 7 | createTokenRecord, 8 | getIsTokenLiquid, 9 | } from "../../../shared/src/utils/TokenRecordHelper"; 10 | import { WALLET_ADDRESSES } from "../../../shared/src/Wallets"; 11 | import { BLOCKCHAIN, ERC20_TOKENS_POLYGON, OHM_TOKENS } from "../contracts/Constants"; 12 | import { getContractName } from "../contracts/Contracts"; 13 | import { getPriceRecursive, HANDLERS } from "../price/PriceLookup"; 14 | 15 | /** 16 | * Returns the token records for a given token. This includes: 17 | * - Wallets 18 | * - Allocators 19 | * 20 | * @param contractAddress the address of the ERC20 contract 21 | * @param block the current block 22 | * @returns TokenRecord array 23 | */ 24 | function getOwnedLiquidityBalance( 25 | timestamp: BigInt, 26 | liquidityHandler: PriceHandler, 27 | block: BigInt, 28 | ): TokenRecord[] { 29 | // TODO consider migrating this function to shared folder 30 | const contractName = getContractName(liquidityHandler.getId()); 31 | log.info("getOwnedLiquidityBalance: Calculating balance for {} ({}) at block number {}", [ 32 | contractName, 33 | liquidityHandler.getId(), 34 | block.toString(), 35 | ]); 36 | const records: TokenRecord[] = []; 37 | 38 | // Calculate the multiplier 39 | const totalValue = liquidityHandler.getTotalValue([], getPriceRecursive, block); 40 | if (!totalValue || totalValue.equals(BigDecimal.zero())) { 41 | return records; 42 | } 43 | const includedValue = liquidityHandler.getTotalValue(OHM_TOKENS, getPriceRecursive, block); 44 | if (!includedValue) { 45 | return records; 46 | } 47 | const multiplier = includedValue.div(totalValue); 48 | 49 | // Get the unit rate 50 | const unitRate = liquidityHandler.getUnitPrice(getPriceRecursive, block); 51 | if (!unitRate) { 52 | return records; 53 | } 54 | 55 | for (let i = 0; i < WALLET_ADDRESSES.length; i++) { 56 | const currentWalletAddress = WALLET_ADDRESSES[i]; 57 | 58 | // Get the balance 59 | const balance = liquidityHandler.getBalance(currentWalletAddress, block); 60 | if (balance.equals(BigDecimal.zero())) { 61 | continue; 62 | } 63 | 64 | // Create record 65 | const record = createTokenRecord( 66 | timestamp, 67 | getContractName(liquidityHandler.getId()), 68 | liquidityHandler.getId(), 69 | getContractName(currentWalletAddress), 70 | currentWalletAddress, 71 | unitRate, 72 | balance, 73 | block, 74 | getIsTokenLiquid(liquidityHandler.getId(), ERC20_TOKENS_POLYGON), 75 | ERC20_TOKENS_POLYGON, 76 | BLOCKCHAIN, 77 | multiplier, 78 | ); 79 | records.push(record); 80 | } 81 | 82 | return records; 83 | } 84 | 85 | /** 86 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 87 | * 88 | * @param timestamp 89 | * @param blockNumber the current block 90 | * @returns TokenRecord array 91 | */ 92 | export function getOwnedLiquidityBalances(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 93 | const records: TokenRecord[] = []; 94 | 95 | for (let i = 0; i < HANDLERS.length; i++) { 96 | pushTokenRecordArray(records, getOwnedLiquidityBalance(timestamp, HANDLERS[i], blockNumber)); 97 | } 98 | 99 | return records; 100 | } 101 | -------------------------------------------------------------------------------- /subgraphs/shared/tests/price/PriceRouter.test.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | import { assert, describe, test } from "matchstick-as/assembly/index"; 3 | 4 | import { PriceLookup, PriceLookupResult } from "../../src/price/PriceHandler"; 5 | import { getUSDRate } from "../../src/price/PriceRouter"; 6 | import { PriceHandlerCustom } from "./PriceHandlerCustom"; 7 | 8 | const TOKEN = "0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5"; // OHM V2 9 | 10 | describe("getUSDRate", () => { 11 | test("no handlers", () => { 12 | const priceLookup: PriceLookup = ( 13 | _address: string, 14 | _block: BigInt, 15 | ): PriceLookupResult | null => { 16 | return null; 17 | }; 18 | 19 | const result: PriceLookupResult | null = getUSDRate( 20 | TOKEN, 21 | [], 22 | priceLookup, 23 | BigInt.fromString("1"), 24 | ); 25 | assert.assertTrue(result === null); 26 | }); 27 | 28 | test("one handler", () => { 29 | const priceLookup: PriceLookup = ( 30 | _address: string, 31 | _block: BigInt, 32 | ): PriceLookupResult | null => { 33 | return { 34 | price: BigDecimal.fromString("1"), 35 | liquidity: BigDecimal.fromString("1"), 36 | }; 37 | }; 38 | 39 | const customHandler = new PriceHandlerCustom({ 40 | price: BigDecimal.fromString("1.234"), 41 | liquidity: BigDecimal.fromString("222222"), 42 | }); 43 | 44 | const result: PriceLookupResult | null = getUSDRate( 45 | TOKEN, 46 | [customHandler], 47 | priceLookup, 48 | BigInt.fromString("1"), 49 | ); 50 | assert.stringEquals("1.234", result ? result.price.toString() : ""); 51 | }); 52 | 53 | test("chooses highest liquidity", () => { 54 | const priceLookup: PriceLookup = ( 55 | _address: string, 56 | _block: BigInt, 57 | ): PriceLookupResult | null => { 58 | return { 59 | price: BigDecimal.fromString("1"), 60 | liquidity: BigDecimal.fromString("1"), 61 | }; 62 | }; 63 | 64 | const customHandler = new PriceHandlerCustom({ 65 | price: BigDecimal.fromString("1.5"), 66 | liquidity: BigDecimal.fromString("222222"), 67 | }); 68 | 69 | const customHandlerTwo = new PriceHandlerCustom({ 70 | price: BigDecimal.fromString("1.234"), 71 | liquidity: BigDecimal.fromString("222223"), // Higher 72 | }); 73 | 74 | const result: PriceLookupResult | null = getUSDRate( 75 | TOKEN, 76 | [customHandler, customHandlerTwo], 77 | priceLookup, 78 | BigInt.fromString("1"), 79 | ); 80 | // customHandlerTwo is selected, due to the higher liquidity 81 | assert.stringEquals("1.234", result ? result.price.toString() : ""); 82 | }); 83 | 84 | test("avoids infinite loop", () => { 85 | const customHandler = new PriceHandlerCustom({ 86 | price: BigDecimal.fromString("1.234"), 87 | liquidity: BigDecimal.fromString("222222"), 88 | }); 89 | 90 | // This should trigger an infinite loop 91 | const priceLookup: PriceLookup = ( 92 | _address: string, 93 | _block: BigInt, 94 | ): PriceLookupResult | null => { 95 | return { 96 | price: BigDecimal.fromString("1"), 97 | liquidity: BigDecimal.fromString("1"), 98 | }; 99 | }; 100 | 101 | const result: PriceLookupResult | null = getUSDRate( 102 | TOKEN, 103 | [customHandler], 104 | priceLookup, 105 | BigInt.fromString("1"), 106 | customHandler.getId(), 107 | ); 108 | assert.assertNull(result); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /subgraphs/fantom/src/treasury/OwnedLiquidity.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { PriceHandler } from "../../../shared/src/price/PriceHandler"; 5 | import { pushTokenRecordArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { 7 | createTokenRecord, 8 | getIsTokenLiquid, 9 | } from "../../../shared/src/utils/TokenRecordHelper"; 10 | import { BLOCKCHAIN, ERC20_TOKENS_FANTOM, OHM_TOKENS } from "../contracts/Constants"; 11 | import { getContractName } from "../contracts/Contracts"; 12 | import { FANTOM_PROTOCOL_ADDRESSES } from "../contracts/ProtocolAddresses"; 13 | import { getPriceRecursive, HANDLERS } from "../price/PriceLookup"; 14 | 15 | /** 16 | * Returns the token records for a given token. This includes: 17 | * - Wallets 18 | * - Allocators 19 | * 20 | * @param contractAddress the address of the ERC20 contract 21 | * @param block the current block 22 | * @returns TokenRecord array 23 | */ 24 | function getOwnedLiquidityBalance( 25 | timestamp: BigInt, 26 | liquidityHandler: PriceHandler, 27 | block: BigInt, 28 | ): TokenRecord[] { 29 | // TODO consider migrating this function to shared folder 30 | const contractName = getContractName(liquidityHandler.getId()); 31 | log.info("getOwnedLiquidityBalance: Calculating balance for {} ({}) at block number {}", [ 32 | contractName, 33 | liquidityHandler.getId(), 34 | block.toString(), 35 | ]); 36 | const records: TokenRecord[] = []; 37 | 38 | // Calculate the multiplier 39 | const totalValue = liquidityHandler.getTotalValue([], getPriceRecursive, block); 40 | if (!totalValue || totalValue.equals(BigDecimal.zero())) { 41 | return records; 42 | } 43 | const includedValue = liquidityHandler.getTotalValue(OHM_TOKENS, getPriceRecursive, block); 44 | if (!includedValue) { 45 | return records; 46 | } 47 | const multiplier = includedValue.div(totalValue); 48 | 49 | // Get the unit rate 50 | const unitRate = liquidityHandler.getUnitPrice(getPriceRecursive, block); 51 | if (!unitRate) { 52 | return records; 53 | } 54 | 55 | for (let i = 0; i < FANTOM_PROTOCOL_ADDRESSES.length; i++) { 56 | const currentWalletAddress = FANTOM_PROTOCOL_ADDRESSES[i]; 57 | 58 | // Get the balance 59 | const balance = liquidityHandler.getBalance(currentWalletAddress, block); 60 | if (balance.equals(BigDecimal.zero())) { 61 | continue; 62 | } 63 | 64 | // Create record 65 | const record = createTokenRecord( 66 | timestamp, 67 | getContractName(liquidityHandler.getId()), 68 | liquidityHandler.getId(), 69 | getContractName(currentWalletAddress), 70 | currentWalletAddress, 71 | unitRate, 72 | balance, 73 | block, 74 | getIsTokenLiquid(liquidityHandler.getId(), ERC20_TOKENS_FANTOM), 75 | ERC20_TOKENS_FANTOM, 76 | BLOCKCHAIN, 77 | multiplier, 78 | ); 79 | records.push(record); 80 | } 81 | 82 | return records; 83 | } 84 | 85 | /** 86 | * Gets the balances for all tokens in the category, using {getTokenBalance}. 87 | * 88 | * @param timestamp 89 | * @param blockNumber the current block 90 | * @returns TokenRecord array 91 | */ 92 | export function getOwnedLiquidityBalances(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 93 | const records: TokenRecord[] = []; 94 | 95 | for (let i = 0; i < HANDLERS.length; i++) { 96 | pushTokenRecordArray(records, getOwnedLiquidityBalance(timestamp, HANDLERS[i], blockNumber)); 97 | } 98 | 99 | return records; 100 | } 101 | -------------------------------------------------------------------------------- /subgraphs/fantom/src/contracts/Contracts.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20 } from "../../../shared/generated/Price/ERC20"; 4 | import { TokenRecord } from "../../../shared/generated/schema"; 5 | import { getERC20TokenRecordFromWallet } from "../../../shared/src/contracts/ERC20"; 6 | import { 7 | BLOCKCHAIN, 8 | CONTRACT_ABBREVIATION_MAP, 9 | CONTRACT_NAME_MAP, 10 | ERC20_TOKENS_FANTOM, 11 | } from "./Constants"; 12 | import { FANTOM_PROTOCOL_ADDRESSES } from "./ProtocolAddresses"; 13 | 14 | export function getContractName( 15 | contractAddress: string, 16 | suffix: string | null = null, 17 | abbreviation: string | null = null, 18 | ): string { 19 | const contractAddressLower = contractAddress.toLowerCase(); 20 | 21 | const contractName = CONTRACT_NAME_MAP.has(contractAddressLower) 22 | ? CONTRACT_NAME_MAP.get(contractAddressLower) 23 | : contractAddressLower; 24 | 25 | // Suffix 26 | const contractSuffix = suffix ? ` - ${suffix}` : ""; 27 | 28 | // Abbreviation 29 | const contractAbbreviation = abbreviation 30 | ? ` (${abbreviation})` 31 | : CONTRACT_ABBREVIATION_MAP.has(contractAddressLower) 32 | ? ` (${CONTRACT_ABBREVIATION_MAP.get(contractAddressLower)})` 33 | : ""; 34 | 35 | return `${contractName}${contractSuffix}${contractAbbreviation}`; 36 | } 37 | 38 | const NON_TREASURY_ASSET_WHITELIST = new Map(); 39 | 40 | /** 41 | * Some wallets (e.g. {DAO_WALLET}) have specific treasury assets mixed into them. 42 | * For this reason, the wallets to be used differ on a per-contract basis. 43 | * 44 | * This function returns the wallets that should be iterated over for the given 45 | * contract, {contractAddress}. 46 | * 47 | * @param contractAddress 48 | * @returns 49 | */ 50 | export const getWalletAddressesForContract = (contractAddress: string): string[] => { 51 | const nonTreasuryAddresses = NON_TREASURY_ASSET_WHITELIST.has(contractAddress.toLowerCase()) 52 | ? NON_TREASURY_ASSET_WHITELIST.get(contractAddress.toLowerCase()) 53 | : []; 54 | const newAddresses = FANTOM_PROTOCOL_ADDRESSES.slice(0); 55 | 56 | // Add the values of nonTreasuryAddresses, but filter duplicates 57 | for (let i = 0; i < nonTreasuryAddresses.length; i++) { 58 | const currentValue = nonTreasuryAddresses[i]; 59 | 60 | if (newAddresses.includes(currentValue)) { 61 | continue; 62 | } 63 | 64 | newAddresses.push(currentValue); 65 | } 66 | 67 | return newAddresses; 68 | }; 69 | 70 | /** 71 | * Fetches the balances of the given ERC20 token from 72 | * the wallets defined in {getWalletAddressesForContract}. 73 | * 74 | * @param metricName The name of the current metric, which is used for entity ids 75 | * @param contractAddress ERC20 contract address 76 | * @param contract ERC20 contract 77 | * @param rate the unit price/rate of the token 78 | * @param blockNumber the current block number 79 | * @returns TokenRecord array 80 | */ 81 | export function getERC20TokenRecordsFromWallets( 82 | timestamp: BigInt, 83 | contractAddress: string, 84 | contract: ERC20, 85 | rate: BigDecimal, 86 | blockNumber: BigInt, 87 | ): TokenRecord[] { 88 | const records: TokenRecord[] = []; 89 | const wallets = getWalletAddressesForContract(contractAddress); 90 | 91 | for (let i = 0; i < wallets.length; i++) { 92 | const record = getERC20TokenRecordFromWallet( 93 | timestamp, 94 | contractAddress, 95 | wallets[i], 96 | contract, 97 | rate, 98 | blockNumber, 99 | getContractName, 100 | ERC20_TOKENS_FANTOM, 101 | BLOCKCHAIN, 102 | ); 103 | if (!record) continue; 104 | 105 | records.push(record); 106 | } 107 | 108 | return records; 109 | } 110 | -------------------------------------------------------------------------------- /subgraphs/polygon/src/contracts/Contracts.ts: -------------------------------------------------------------------------------- 1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { ERC20 } from "../../../shared/generated/Price/ERC20"; 4 | import { TokenRecord } from "../../../shared/generated/schema"; 5 | import { getERC20TokenRecordFromWallet } from "../../../shared/src/contracts/ERC20"; 6 | import { DAO_WALLET, WALLET_ADDRESSES } from "../../../shared/src/Wallets"; 7 | import { 8 | BLOCKCHAIN, 9 | CONTRACT_ABBREVIATION_MAP, 10 | CONTRACT_NAME_MAP, 11 | ERC20_TOKENS_POLYGON, 12 | ERC20_WETH, 13 | } from "./Constants"; 14 | 15 | export function getContractName( 16 | contractAddress: string, 17 | suffix: string | null = null, 18 | abbreviation: string | null = null, 19 | ): string { 20 | const contractAddressLower = contractAddress.toLowerCase(); 21 | 22 | const contractName = CONTRACT_NAME_MAP.has(contractAddressLower) 23 | ? CONTRACT_NAME_MAP.get(contractAddressLower) 24 | : contractAddressLower; 25 | 26 | // Suffix 27 | const contractSuffix = suffix ? ` - ${suffix}` : ""; 28 | 29 | // Abbreviation 30 | const contractAbbreviation = abbreviation 31 | ? ` (${abbreviation})` 32 | : CONTRACT_ABBREVIATION_MAP.has(contractAddressLower) 33 | ? ` (${CONTRACT_ABBREVIATION_MAP.get(contractAddressLower)})` 34 | : ""; 35 | 36 | return `${contractName}${contractSuffix}${contractAbbreviation}`; 37 | } 38 | 39 | const NON_TREASURY_ASSET_WHITELIST = new Map(); 40 | NON_TREASURY_ASSET_WHITELIST.set(ERC20_WETH, [DAO_WALLET]); 41 | 42 | /** 43 | * Some wallets (e.g. {DAO_WALLET}) have specific treasury assets mixed into them. 44 | * For this reason, the wallets to be used differ on a per-contract basis. 45 | * 46 | * This function returns the wallets that should be iterated over for the given 47 | * contract, {contractAddress}. 48 | * 49 | * @param contractAddress 50 | * @returns 51 | */ 52 | export const getWalletAddressesForContract = (contractAddress: string): string[] => { 53 | const nonTreasuryAddresses = NON_TREASURY_ASSET_WHITELIST.has(contractAddress.toLowerCase()) 54 | ? NON_TREASURY_ASSET_WHITELIST.get(contractAddress.toLowerCase()) 55 | : []; 56 | const newAddresses = WALLET_ADDRESSES.slice(0); 57 | 58 | // Add the values of nonTreasuryAddresses, but filter duplicates 59 | for (let i = 0; i < nonTreasuryAddresses.length; i++) { 60 | const currentValue = nonTreasuryAddresses[i]; 61 | 62 | if (newAddresses.includes(currentValue)) { 63 | continue; 64 | } 65 | 66 | newAddresses.push(currentValue); 67 | } 68 | 69 | return newAddresses; 70 | }; 71 | 72 | /** 73 | * Fetches the balances of the given ERC20 token from 74 | * the wallets defined in {getWalletAddressesForContract}. 75 | * 76 | * @param metricName The name of the current metric, which is used for entity ids 77 | * @param contractAddress ERC20 contract address 78 | * @param contract ERC20 contract 79 | * @param rate the unit price/rate of the token 80 | * @param blockNumber the current block number 81 | * @returns TokenRecord array 82 | */ 83 | export function getERC20TokenRecordsFromWallets( 84 | timestamp: BigInt, 85 | contractAddress: string, 86 | contract: ERC20, 87 | rate: BigDecimal, 88 | blockNumber: BigInt, 89 | ): TokenRecord[] { 90 | const records: TokenRecord[] = []; 91 | const wallets = getWalletAddressesForContract(contractAddress); 92 | 93 | for (let i = 0; i < wallets.length; i++) { 94 | const record = getERC20TokenRecordFromWallet( 95 | timestamp, 96 | contractAddress, 97 | wallets[i], 98 | contract, 99 | rate, 100 | blockNumber, 101 | getContractName, 102 | ERC20_TOKENS_POLYGON, 103 | BLOCKCHAIN, 104 | ); 105 | if (!record) continue; 106 | 107 | records.push(record); 108 | } 109 | 110 | return records; 111 | } 112 | -------------------------------------------------------------------------------- /subgraphs/ethereum/src/utils/TreasuryCalculations.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { TokenSupply } from "../../../shared/generated/schema"; 5 | import { pushTokenRecordArray, pushTokenSupplyArray } from "../../../shared/src/utils/ArrayHelper"; 6 | import { getClearinghouseReceivables } from "../contracts/CoolerLoansClearinghouse"; 7 | import { getCoolerV2Receivables } from "../contracts/CoolerLoansV2Monocooler"; 8 | import { getOwnedLiquidityPoolValue } from "../liquidity/LiquidityCalculations"; 9 | import { getAllERC4626Balances } from "./ERC4626"; 10 | import { 11 | getBoostedLiquiditySupplyRecords, 12 | getIncurDebtSupplyRecords, 13 | getMintedBorrowableOHMRecords, 14 | getProtocolOwnedLiquiditySupplyRecords, 15 | getTotalSupplyRecord, 16 | getTreasuryOHMRecords, 17 | getVestingBondSupplyRecords, 18 | } from "./OhmCalculations"; 19 | import { getStablecoinBalances } from "./TokenStablecoins"; 20 | import { getVolatileTokenBalances } from "./TokenVolatile"; 21 | 22 | /** 23 | * Returns the market value, which is composed of: 24 | * - stable value (getStablecoinBalances) 25 | * - volatile value (getVolatileTokenBalances) 26 | * - protocol-owned liquidity value (getOwnedLiquidityPoolValue) 27 | * 28 | * Market value does not exclude the value of OHM in the treasury. 29 | * 30 | * @param blockNumber 31 | * @returns 32 | */ 33 | export function generateTokenRecords(timestamp: BigInt, blockNumber: BigInt): TokenRecord[] { 34 | const records: TokenRecord[] = []; 35 | 36 | // Stable without protocol-owned liquidity 37 | pushTokenRecordArray( 38 | records, 39 | getStablecoinBalances(timestamp, false, blockNumber), 40 | ); 41 | 42 | // Volatile without protocol-owned liquidity, but blue-chip assets 43 | pushTokenRecordArray( 44 | records, 45 | getVolatileTokenBalances(timestamp, false, false, true, blockNumber), 46 | ); 47 | 48 | // Protocol-owned liquidity 49 | pushTokenRecordArray( 50 | records, 51 | getOwnedLiquidityPoolValue(timestamp, blockNumber), 52 | ); 53 | 54 | // Get ERC4626 tokens 55 | pushTokenRecordArray( 56 | records, 57 | getAllERC4626Balances(timestamp, blockNumber), 58 | ); 59 | 60 | // Get Clearinghouse receivables 61 | pushTokenRecordArray( 62 | records, 63 | getClearinghouseReceivables(timestamp, blockNumber), 64 | ); 65 | 66 | // Get CoolerV2 receivables 67 | pushTokenRecordArray( 68 | records, 69 | getCoolerV2Receivables(timestamp, blockNumber), 70 | ); 71 | 72 | return records; 73 | } 74 | 75 | export function generateTokenSupply(timestamp: BigInt, blockNumber: BigInt): TokenSupply[] { 76 | const records: TokenSupply[] = []; 77 | 78 | // OHM 79 | // Total supply 80 | pushTokenSupplyArray( 81 | records, 82 | [getTotalSupplyRecord(timestamp, blockNumber)], 83 | ); 84 | 85 | // Treasury OHM & migration offset 86 | pushTokenSupplyArray( 87 | records, 88 | getTreasuryOHMRecords(timestamp, blockNumber), 89 | ); 90 | 91 | // POL 92 | pushTokenSupplyArray( 93 | records, 94 | getProtocolOwnedLiquiditySupplyRecords(timestamp, blockNumber), 95 | ); 96 | 97 | // Bond vesting 98 | pushTokenSupplyArray( 99 | records, 100 | getVestingBondSupplyRecords(timestamp, blockNumber), 101 | ); 102 | 103 | // Borrowable OHM 104 | pushTokenSupplyArray( 105 | records, 106 | getMintedBorrowableOHMRecords(timestamp, blockNumber), 107 | ); 108 | 109 | // OHM in Boosted Liquidity Vaults 110 | pushTokenSupplyArray( 111 | records, 112 | getBoostedLiquiditySupplyRecords(timestamp, blockNumber), 113 | ); 114 | 115 | // OHM in IncurDebt 116 | pushTokenSupplyArray( 117 | records, 118 | getIncurDebtSupplyRecords(timestamp, blockNumber), 119 | ); 120 | 121 | return records; 122 | } 123 | -------------------------------------------------------------------------------- /bin/subgraph/src/helpers/metrics.ts: -------------------------------------------------------------------------------- 1 | import { TokenRecord, TokenSupply } from "../subgraph"; 2 | 3 | // Source: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph/blob/master/subgraphs/ethereum/src/utils/TokenSupplyHelper.ts 4 | const TYPE_BONDS_DEPOSITS = "OHM Bonds (Burnable Deposits)"; 5 | const TYPE_BONDS_PREMINTED = "OHM Bonds (Pre-minted)"; 6 | const TYPE_BONDS_VESTING_DEPOSITS = "OHM Bonds (Vesting Deposits)"; 7 | const TYPE_BOOSTED_LIQUIDITY_VAULT = "Boosted Liquidity Vault"; 8 | const TYPE_LENDING = "Lending"; 9 | const TYPE_LIQUIDITY = "Liquidity"; 10 | const TYPE_OFFSET = "Manual Offset"; 11 | const TYPE_TOTAL_SUPPLY = "Total Supply"; 12 | const TYPE_TREASURY = "Treasury"; 13 | 14 | export const calculateMarketValue = (records: TokenRecord[]): number => { 15 | return records.reduce((previousValue, record) => { 16 | return previousValue + +record.value; 17 | }, 0); 18 | }; 19 | 20 | export const calculateMarketValueCategory = (records: TokenRecord[], category: string): number => { 21 | return records 22 | .filter((record) => record.category == category) 23 | .reduce((previousValue, record) => { 24 | return previousValue + +record.value; 25 | }, 0); 26 | }; 27 | 28 | export const calculateLiquidBacking = (records: TokenRecord[]): number => { 29 | return records 30 | .filter((record) => record.isLiquid == true) 31 | .reduce((previousValue, record) => { 32 | return previousValue + +record.valueExcludingOhm; 33 | }, 0); 34 | }; 35 | 36 | export const calculateSupplySum = (records: TokenSupply[]): number => { 37 | return records ? records.reduce((previousValue, record) => { 38 | return previousValue + +record.supplyBalance; 39 | }, 0) : 0; 40 | } 41 | 42 | // Source: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph/blob/9ef60c7c2be9fc9b45dd98dd119c0fa5cefb4760/subgraphs/ethereum/src/utils/OhmCalculations.ts#L673 43 | export const calculateCirculatingSupply = (records: TokenSupply[]): number => { 44 | let total = 0; 45 | 46 | const includedTypes = [TYPE_TOTAL_SUPPLY, TYPE_TREASURY, TYPE_OFFSET, TYPE_BONDS_PREMINTED, TYPE_BONDS_VESTING_DEPOSITS, TYPE_BONDS_DEPOSITS, TYPE_BOOSTED_LIQUIDITY_VAULT]; 47 | 48 | for (let i = 0; i < records.length; i++) { 49 | const tokenSupply = records[i]; 50 | 51 | if (!includedTypes.includes(tokenSupply.type)) { 52 | continue; 53 | } 54 | 55 | total += Number(tokenSupply.supplyBalance); 56 | } 57 | 58 | return total; 59 | }; 60 | 61 | // Source: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph/blob/9ef60c7c2be9fc9b45dd98dd119c0fa5cefb4760/subgraphs/ethereum/src/utils/OhmCalculations.ts#L643 62 | export const calculateFloatingSupply = (records: TokenSupply[]): number => { 63 | let total = 0; 64 | 65 | const includedTypes = [TYPE_TOTAL_SUPPLY, TYPE_TREASURY, TYPE_OFFSET, TYPE_BONDS_PREMINTED, TYPE_BONDS_VESTING_DEPOSITS, TYPE_BONDS_DEPOSITS, TYPE_BOOSTED_LIQUIDITY_VAULT, TYPE_LIQUIDITY]; 66 | 67 | for (let i = 0; i < records.length; i++) { 68 | const tokenSupply = records[i]; 69 | 70 | if (!includedTypes.includes(tokenSupply.type)) { 71 | continue; 72 | } 73 | 74 | total += Number(tokenSupply.supplyBalance); 75 | } 76 | 77 | return total; 78 | } 79 | 80 | // Source: https://github.com/OlympusDAO/olympus-protocol-metrics-subgraph/blob/9ef60c7c2be9fc9b45dd98dd119c0fa5cefb4760/subgraphs/ethereum/src/utils/OhmCalculations.ts#L612 81 | export const calculateBackedSupply = (records: TokenSupply[]): number => { 82 | let total = 0; 83 | 84 | const includedTypes = [TYPE_TOTAL_SUPPLY, TYPE_TREASURY, TYPE_OFFSET, TYPE_BONDS_PREMINTED, TYPE_BONDS_VESTING_DEPOSITS, TYPE_BONDS_DEPOSITS, TYPE_BOOSTED_LIQUIDITY_VAULT, TYPE_LIQUIDITY, TYPE_LENDING]; 85 | 86 | for (let i = 0; i < records.length; i++) { 87 | const tokenSupply = records[i]; 88 | 89 | if (!includedTypes.includes(tokenSupply.type)) { 90 | continue; 91 | } 92 | 93 | total += Number(tokenSupply.supplyBalance); 94 | } 95 | 96 | return total; 97 | } 98 | -------------------------------------------------------------------------------- /subgraphs/arbitrum/src/contracts/TreasureMining.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt } from "@graphprotocol/graph-ts"; 2 | 3 | import { TokenRecord } from "../../../shared/generated/schema"; 4 | import { toDecimal } from "../../../shared/src/utils/Decimals"; 5 | import { addressesEqual } from "../../../shared/src/utils/StringHelper"; 6 | import { createTokenRecord } from "../../../shared/src/utils/TokenRecordHelper"; 7 | import { TreasureMining } from "../../generated/TokenRecords-arbitrum/TreasureMining"; 8 | import { getPrice } from "../price/PriceLookup"; 9 | import { BLOCKCHAIN, ERC20_MAGIC, ERC20_TOKENS_ARBITRUM, getProtocolAddresses, TREASURE_ATLAS_MINE } from "./Constants"; 10 | import { getContractName } from "./Contracts"; 11 | 12 | /** 13 | * Convenience method to get the staking contract 14 | * 15 | * @param _block 16 | * @returns 17 | */ 18 | const getStakingContract = (_block: BigInt): TreasureMining => { 19 | const contract = TreasureMining.bind(Address.fromString(TREASURE_ATLAS_MINE)); 20 | 21 | return contract; 22 | }; 23 | 24 | /** 25 | * Determines the deposit IDs for the given {walletAddress} 26 | * 27 | * @param walletAddress 28 | * @param contract 29 | * @param _block 30 | * @returns 31 | */ 32 | const getUserDepositIds = ( 33 | walletAddress: string, 34 | contract: TreasureMining, 35 | _block: BigInt, 36 | ): BigInt[] => { 37 | const result = contract.try_getAllUserDepositIds(Address.fromString(walletAddress)); 38 | if (result.reverted) { 39 | return []; 40 | } 41 | 42 | return result.value; 43 | }; 44 | 45 | /** 46 | * Returns the staked balance of MAGIC belonging to {walletAddress} 47 | * in the TreasureDAO staking pool {poolId}. 48 | * 49 | * @param tokenAddress 50 | * @param walletAddress 51 | * @param poolId 52 | * @param _block 53 | * @returns 54 | */ 55 | const getStakedBalance = ( 56 | tokenAddress: string, 57 | walletAddress: string, 58 | poolId: BigInt, 59 | contract: TreasureMining, 60 | _block: BigInt, 61 | ): BigDecimal => { 62 | if (!addressesEqual(tokenAddress, ERC20_MAGIC)) { 63 | return BigDecimal.zero(); 64 | } 65 | 66 | return toDecimal( 67 | contract.userInfo(Address.fromString(walletAddress), poolId).getDepositAmount(), 68 | 18, 69 | ); 70 | }; 71 | 72 | /** 73 | * Returns the balances of {tokenAddress} across all wallets 74 | * staked with TreasureDAO. 75 | * 76 | * @param timestamp 77 | * @param tokenAddress 78 | * @param block 79 | * @returns 80 | */ 81 | export const getStakedBalances = ( 82 | timestamp: BigInt, 83 | tokenAddress: string, 84 | block: BigInt, 85 | ): TokenRecord[] => { 86 | const records: TokenRecord[] = []; 87 | let price: BigDecimal | null = null; 88 | 89 | const contract = getStakingContract(block); 90 | 91 | const allWallets = getProtocolAddresses(); 92 | for (let i = 0; i < allWallets.length; i++) { 93 | const walletAddress = allWallets[i]; 94 | const depositIds = getUserDepositIds(walletAddress, contract, block); 95 | 96 | for (let j = 0; j < depositIds.length; j++) { 97 | const balance = getStakedBalance(tokenAddress, walletAddress, depositIds[j], contract, block); 98 | 99 | if (balance.equals(BigDecimal.zero())) { 100 | continue; 101 | } 102 | 103 | // This avoids doing price lookups until there is a wallet/token/poolId match 104 | if (!price) { 105 | price = getPrice(tokenAddress, block); 106 | } 107 | 108 | const record = createTokenRecord( 109 | timestamp, 110 | getContractName(tokenAddress, "Staked", "veMAGIC"), 111 | tokenAddress, 112 | getContractName(walletAddress), 113 | walletAddress, 114 | price, 115 | balance, 116 | block, 117 | false, // Locked 118 | ERC20_TOKENS_ARBITRUM, 119 | BLOCKCHAIN, 120 | ); 121 | records.push(record); 122 | } 123 | } 124 | 125 | return records; 126 | }; 127 | --------------------------------------------------------------------------------