From 086163c3776efef9b831af22a47517e0b03b51eb Mon Sep 17 00:00:00 2001 From: Chris Jacobs Date: Tue, 17 Oct 2023 17:56:43 -0700 Subject: [PATCH 1/4] feat: data requirements thoughts (start v8) - aave balances --- ...01723589-Data.js => 1697589427921-Data.js} | 10 +++- docs/REQUIREMENTS.md | 9 ++-- schema-base.graphql | 12 +++++ schema.graphql | 12 +++++ src/main.ts | 5 +- src/model/generated/balance.model.ts | 32 +++++++++++ src/model/generated/index.ts | 1 + src/processor-templates/curve/curve.ts | 6 +-- .../erc20-balance/erc20-balance.ts | 53 +++++++++++++++++++ .../erc20-balance/index.ts | 1 + src/processors/aave-compound/aave-compound.ts | 40 ++++++++++++++ src/processors/aave-compound/index.ts | 1 + 12 files changed, 171 insertions(+), 11 deletions(-) rename db/migrations/{1697501723589-Data.js => 1697589427921-Data.js} (97%) create mode 100644 src/model/generated/balance.model.ts create mode 100644 src/processor-templates/erc20-balance/erc20-balance.ts create mode 100644 src/processor-templates/erc20-balance/index.ts create mode 100644 src/processors/aave-compound/aave-compound.ts create mode 100644 src/processors/aave-compound/index.ts diff --git a/db/migrations/1697501723589-Data.js b/db/migrations/1697589427921-Data.js similarity index 97% rename from db/migrations/1697501723589-Data.js rename to db/migrations/1697589427921-Data.js index 82fa31ce..a5e2f4cc 100644 --- a/db/migrations/1697501723589-Data.js +++ b/db/migrations/1697589427921-Data.js @@ -1,10 +1,13 @@ -module.exports = class Data1697501723589 { - name = 'Data1697501723589' +module.exports = class Data1697589427921 { + name = 'Data1697589427921' async up(db) { await db.query(`CREATE TABLE "exchange_rate" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "pair" text NOT NULL, "base" text NOT NULL, "quote" text NOT NULL, "rate" numeric NOT NULL, CONSTRAINT "PK_5c5d27d2b900ef6cdeef0398472" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_9e23a3f1bf3634820c873a0fe8" ON "exchange_rate" ("timestamp") `) await db.query(`CREATE INDEX "IDX_c61a93768eed9e58ce399bbe01" ON "exchange_rate" ("block_number") `) + await db.query(`CREATE TABLE "balance" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "token" text NOT NULL, "address" text NOT NULL, "balance" numeric NOT NULL, CONSTRAINT "PK_079dddd31a81672e8143a649ca0" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_a956b410c329b8eca7898c3c51" ON "balance" ("timestamp") `) + await db.query(`CREATE INDEX "IDX_6b451b59c9f6a6fdd685f530b2" ON "balance" ("block_number") `) await db.query(`CREATE TABLE "curve_pool_balance" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "address" text NOT NULL, "balance0" numeric NOT NULL, "balance1" numeric NOT NULL, "balance2" numeric NOT NULL, CONSTRAINT "PK_40412750bb910ca560aa084dd88" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_ffb0d0f86f03faacef7cb3e092" ON "curve_pool_balance" ("timestamp") `) await db.query(`CREATE INDEX "IDX_db5522c865eb8ed76fa7aeb4a8" ON "curve_pool_balance" ("block_number") `) @@ -121,6 +124,9 @@ module.exports = class Data1697501723589 { await db.query(`DROP TABLE "exchange_rate"`) await db.query(`DROP INDEX "public"."IDX_9e23a3f1bf3634820c873a0fe8"`) await db.query(`DROP INDEX "public"."IDX_c61a93768eed9e58ce399bbe01"`) + await db.query(`DROP TABLE "balance"`) + await db.query(`DROP INDEX "public"."IDX_a956b410c329b8eca7898c3c51"`) + await db.query(`DROP INDEX "public"."IDX_6b451b59c9f6a6fdd685f530b2"`) await db.query(`DROP TABLE "curve_pool_balance"`) await db.query(`DROP INDEX "public"."IDX_ffb0d0f86f03faacef7cb3e092"`) await db.query(`DROP INDEX "public"."IDX_db5522c865eb8ed76fa7aeb4a8"`) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 2f56e6de..7cfc2ab1 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -179,15 +179,15 @@ balanceOf `address` from the map below. ```javascript const aaveAssetToPlatformMap = { - USDT: { + USDT: { // 0xdac17f958d2ee523a2206206994597c13d831ec7 token: "aUSDT", address: addresses.aUSDT, // 0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811 }, - USDC: { + USDC: { // 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 token: "aUSDC", address: addresses.aUSDC, // 0xBcca60bB61934080951369a648Fb03DF4F96263C }, - DAI: { + DAI: { // 0x6b175474e89094c44da98b954eedeac495271d0f token: "aDAI", address: addresses.aDAI, // 0x028171bca77440897b824ca71d1c56cac55b68a3 }, @@ -198,8 +198,9 @@ const aaveAssetToPlatformMap = { - ✅ OETH - This is done for OETH in Subsquid. -- OUSD +- ✅⚠️ OUSD - A virtually identical implementation should work for OUSD. + - TODO items exist For each of the above: diff --git a/schema-base.graphql b/schema-base.graphql index 846f4551..b3da7a6d 100644 --- a/schema-base.graphql +++ b/schema-base.graphql @@ -16,6 +16,18 @@ type ExchangeRate @entity { rate: BigInt! } +type Balance @entity { + """ + Format: 'token:address:blockNumber' + """ + id: ID! + timestamp: DateTime! @index + blockNumber: Int! @index + token: String! + address: String! + balance: BigInt! +} + enum RebasingOption { OptIn OptOut diff --git a/schema.graphql b/schema.graphql index 3f387663..1878b3de 100644 --- a/schema.graphql +++ b/schema.graphql @@ -18,6 +18,18 @@ type ExchangeRate @entity { rate: BigInt! } +type Balance @entity { + """ + Format: 'token:address:blockNumber' + """ + id: ID! + timestamp: DateTime! @index + blockNumber: Int! @index + token: String! + address: String! + balance: BigInt! +} + enum RebasingOption { OptIn OptOut diff --git a/src/main.ts b/src/main.ts index 7f0bf03c..b4f8ff6f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import * as exchangeRates from './post-processors/exchange-rates' import { run } from './processor' +import * as aaveCompound from './processors/aave-compound' import * as curve from './processors/curve' import * as curveLp from './processors/curve-lp' import * as dripper from './processors/dripper' @@ -12,8 +13,8 @@ import * as vault from './processors/vault' run([ { - name: 'curve', - processors: [curve], + name: 'grafana-metrics', + processors: [aaveCompound, curve], }, { name: 'oeth', diff --git a/src/model/generated/balance.model.ts b/src/model/generated/balance.model.ts new file mode 100644 index 00000000..4d5fc326 --- /dev/null +++ b/src/model/generated/balance.model.ts @@ -0,0 +1,32 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import * as marshal from "./marshal" + +@Entity_() +export class Balance { + constructor(props?: Partial) { + Object.assign(this, props) + } + + /** + * Format: 'token:address:blockNumber' + */ + @PrimaryColumn_() + id!: string + + @Index_() + @Column_("timestamp with time zone", {nullable: false}) + timestamp!: Date + + @Index_() + @Column_("int4", {nullable: false}) + blockNumber!: number + + @Column_("text", {nullable: false}) + token!: string + + @Column_("text", {nullable: false}) + address!: string + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + balance!: bigint +} diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index 43b7e16d..6e34eafa 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -1,4 +1,5 @@ export * from "./exchangeRate.model" +export * from "./balance.model" export * from "./curvePoolBalance.model" export * from "./oeth.model" export * from "./oethAddress.model" diff --git a/src/processor-templates/curve/curve.ts b/src/processor-templates/curve/curve.ts index 02a352df..e24f4e5e 100644 --- a/src/processor-templates/curve/curve.ts +++ b/src/processor-templates/curve/curve.ts @@ -20,15 +20,15 @@ export const createCurveSetup = ( processor.includeAllBlocks({ from }) } +let lastBlockHeightProcessed = 0 export const createCurveProcessor = (poolAddress: string, count: number, from: number) => async (ctx: Context) => { const result: ProcessResult = { curvePoolBalances: [], } - let last = 0 const nextBlockIndex = ctx.blocks.findIndex( - (b) => b.header.height >= last + UPDATE_FREQUENCY, + (b) => b.header.height >= lastBlockHeightProcessed + UPDATE_FREQUENCY, ) for (let i = nextBlockIndex; i < ctx.blocks.length; i += UPDATE_FREQUENCY) { const block = ctx.blocks[i] @@ -52,7 +52,7 @@ export const createCurveProcessor = balance2: balances[2] ?? 0n, }) result.curvePoolBalances.push(curve) - last = block.header.height + lastBlockHeightProcessed = block.header.height } await ctx.store.insert(result.curvePoolBalances) } diff --git a/src/processor-templates/erc20-balance/erc20-balance.ts b/src/processor-templates/erc20-balance/erc20-balance.ts new file mode 100644 index 00000000..8cb431a7 --- /dev/null +++ b/src/processor-templates/erc20-balance/erc20-balance.ts @@ -0,0 +1,53 @@ +import { EvmBatchProcessor } from '@subsquid/evm-processor' + +import * as erc20 from '../../abi/erc20' +import { Balance } from '../../model' +import { Context } from '../../processor' + +interface ProcessResult { + balances: Balance[] +} + +export const createER20BalanceSetup = + (from: number) => (processor: EvmBatchProcessor) => { + processor.includeAllBlocks({ from }) + } + +let lastBlockHeightProcessed = 0 +export const createERC20BalanceProcessor = + ({ + from, + address, + token, + frequency, + }: { + from: number + address: string + token: string + frequency: number + }) => + async (ctx: Context) => { + const result: ProcessResult = { + balances: [], + } + const nextBlockIndex = ctx.blocks.findIndex( + (b) => b.header.height >= lastBlockHeightProcessed + frequency, + ) + for (let i = nextBlockIndex; i < ctx.blocks.length; i += frequency) { + const block = ctx.blocks[i] + if (!block || block.header.height < from) continue + const contract = new erc20.Contract(ctx, block.header, token) + + const curve = new Balance({ + id: `${token}:${address}:${block.header.height}`, + blockNumber: block.header.height, + timestamp: new Date(block.header.timestamp), + token, + address, + balance: await contract.balanceOf(address), + }) + result.balances.push(curve) + lastBlockHeightProcessed = block.header.height + } + await ctx.store.insert(result.balances) + } diff --git a/src/processor-templates/erc20-balance/index.ts b/src/processor-templates/erc20-balance/index.ts new file mode 100644 index 00000000..98d9605e --- /dev/null +++ b/src/processor-templates/erc20-balance/index.ts @@ -0,0 +1 @@ +export * from './erc20-balance' diff --git a/src/processors/aave-compound/aave-compound.ts b/src/processors/aave-compound/aave-compound.ts new file mode 100644 index 00000000..5433c35e --- /dev/null +++ b/src/processors/aave-compound/aave-compound.ts @@ -0,0 +1,40 @@ +import { Context } from '../../processor' +import { + createER20BalanceSetup, + createERC20BalanceProcessor, +} from '../../processor-templates/erc20-balance' + +const ESTIMATED_BPS = 12.06 // Circa 2023 +const SECONDS_PER_DAY = 86400 +const BLOCKS_PER_DAY = SECONDS_PER_DAY / ESTIMATED_BPS +const UPDATE_FREQUENCY = Math.floor(BLOCKS_PER_DAY) + +const tracks: Parameters[0][] = [ + { + from: 11362821, + address: '0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811', // aUSDT + token: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + frequency: UPDATE_FREQUENCY, + }, + { + from: 11367200, + address: '0xBcca60bB61934080951369a648Fb03DF4F96263C', // aUSDC + token: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + frequency: UPDATE_FREQUENCY, + }, + { + from: 11367184, + address: '0x028171bca77440897b824ca71d1c56cac55b68a3', // aDAI + token: '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + frequency: UPDATE_FREQUENCY, + }, +] + +export const from = Math.min(...tracks.map((t) => t.from)) + +export const setup = createER20BalanceSetup(from) + +const processors = tracks.map(createERC20BalanceProcessor) +export const process = async (ctx: Context) => { + await Promise.all(processors.map((p) => p(ctx))) +} diff --git a/src/processors/aave-compound/index.ts b/src/processors/aave-compound/index.ts new file mode 100644 index 00000000..3fe7417c --- /dev/null +++ b/src/processors/aave-compound/index.ts @@ -0,0 +1 @@ +export * from './aave-compound' From c48c05c38fc5409bb8ec36fe6261acf29aa016f1 Mon Sep 17 00:00:00 2001 From: Chris Jacobs Date: Tue, 17 Oct 2023 19:14:52 -0700 Subject: [PATCH 2/4] feat: data requirements thoughts (start v8) - notes --- docs/REQUIREMENTS.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 7cfc2ab1..876d7ba1 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -80,16 +80,16 @@ Catalog of data requirements. #### total_supply -Track total supply for **OUSD**, **OGV**, and **OETH**. +Track total supply for **OUSD** ✅, **OGV** ⚠️, and **OETH** ✅. #### vault -Vault price metrics for: `["USDC", "USDT", "DAI"]` +Vault price metrics for: `["USDC", "USDT", "DAI"]` ⚠️ - `vault.priceUnitMint(assetAddress)` - `vault.priceUnitRedeem(assetAddress)` -Vault holdings (`balanceOf`) for: +Vault holdings (`balanceOf`) for: ✅ - OUSD: `["USDC", "USDT", "DAI"]` - OETH: `["WETH", "stETH", "rETH", "frxETH"]` @@ -98,6 +98,10 @@ Vault holdings (`balanceOf`) for: OUSD Holdings: `["USDC", "USDT", "DAI"]` +- ✅ [ousd](..%2Fsrc%2Fprocessors%2Fousd) (vault holdings) + +TODO: ⚠️ + - Compound - Aave - Convex @@ -108,10 +112,14 @@ OUSD Holdings: `["USDC", "USDT", "DAI"]` OETH Holdings: `["frxETH", "rETH", "stETH", "WETH"]` +- ✅ [oeth](..%2Fsrc%2Fprocessors%2Foeth) (vault holdings) + +TODO: ⚠️ + - FraxETH - CurveAMO -#### threePool +#### threePool ✅ [curve](..%2Fsrc%2Fprocessors%2Fcurve) Assets: `["USDC", "USDT", "DAI"]` @@ -127,7 +135,7 @@ contracts.ThreePool contract.balances(threepoolCoinIndexMap[asset]) ``` -#### metapools +#### metapools ✅ [curve](..%2Fsrc%2Fprocessors%2Fcurve) - `curveMetapoolBalanceMetric` - addresses.OUSDMetapool: 0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca @@ -156,7 +164,7 @@ const balancerMetaStablePoolABI = [ ]; ``` -#### oeth +#### oeth ✅ [curve](..%2Fsrc%2Fprocessors%2Fcurve) - curvePoolBalanceMetric: `poolContract.balances(0) or poolContract.balances(1)` - EthFrxEthPool: "0xa1f8a6807c402e4a15ef4eba36528a3fed24e577" - ETH frxETH @@ -165,7 +173,7 @@ const balancerMetaStablePoolABI = [ - WEthStEthPool: "0x828b154032950c8ff7cf8085d841723db2696056" - WETH stETH - OEthEthPool: "0x94B17476A93b3262d87B9a326965D1E91f9c13E7" - OETH ETH -#### aave_comp_platforms +#### aave_comp_platforms ✅ [aave-compound](..%2Fsrc%2Fprocessors%2Faave-compound) - AaveCompoundBorrowableMetric: `"USDC", "USDT", "DAI"` - For each of the below: @@ -196,9 +204,9 @@ const aaveAssetToPlatformMap = { #### rebasing_credits -- ✅ OETH +- ✅ OETH [oeth](..%2Fsrc%2Fprocessors%2Foeth) - This is done for OETH in Subsquid. -- ✅⚠️ OUSD +- ✅⚠️ OUSD [ousd](..%2Fsrc%2Fprocessors%2Fousd) - A virtually identical implementation should work for OUSD. - TODO items exist From cfb1892e4cc5477327b168b5f7d13b2a6da7d4ec Mon Sep 17 00:00:00 2001 From: Chris Jacobs Date: Wed, 18 Oct 2023 18:44:26 -0700 Subject: [PATCH 3/4] feat: grafana prom-metrics - add strategy balances - fix issue with curve and erc20 latest tracking - move oeth related processors into the oeth folder - add `callTo` filter for otoken rebase trace filter - appears to work as intended, as the callTo for the callSighash (the action call to, not the tx call to) - move `owners` into the template context so we don't have to retrieve it for every context - don't add yield history for owners who have no credits - ability to narrow run parameters with PROCESSOR_NAME env - add link to design decisions notion doc --- README.md | 2 + abi/initializable-abstract-strategy.json | 532 ++++++++++++++++++ ...89427921-Data.js => 1697599559759-Data.js} | 10 +- schema-base.graphql | 11 + schema.graphql | 12 +- .../initializable-abstract-strategy.abi.ts | 513 +++++++++++++++++ src/abi/initializable-abstract-strategy.ts | 153 +++++ src/main.ts | 17 +- src/model/generated/index.ts | 1 + src/model/generated/strategyBalance.model.ts | 32 ++ src/processor-templates/curve/curve.ts | 17 +- .../erc20-balance/erc20-balance.ts | 28 +- src/processor-templates/otoken/otoken.ts | 52 +- src/processor-templates/strategy/index.ts | 1 + src/processor-templates/strategy/strategy.ts | 79 +++ src/processor.ts | 4 + .../{aave-compound => }/aave-compound.ts | 4 +- src/processors/aave-compound/index.ts | 1 - src/processors/curve-lp/index.ts | 1 - src/processors/dripper/index.ts | 1 - src/processors/frax-staking/index.ts | 1 - src/processors/morpho-aave/index.ts | 1 - .../balancer-meta-pool.ts | 0 src/processors/{curve-lp => oeth}/curve-lp.ts | 0 src/processors/{dripper => oeth}/dripper.ts | 0 .../{frax-staking => oeth}/frax-staking.ts | 0 .../{morpho-aave => oeth}/morpho-aave.ts | 0 src/processors/{vault => oeth}/vault.ts | 0 src/processors/strategies/index.ts | 1 + src/processors/strategies/strategies.ts | 118 ++++ src/processors/vault/index.ts | 1 - src/utils/range.ts | 3 + 32 files changed, 1533 insertions(+), 63 deletions(-) create mode 100644 abi/initializable-abstract-strategy.json rename db/migrations/{1697589427921-Data.js => 1697599559759-Data.js} (97%) create mode 100644 src/abi/initializable-abstract-strategy.abi.ts create mode 100644 src/abi/initializable-abstract-strategy.ts create mode 100644 src/model/generated/strategyBalance.model.ts create mode 100644 src/processor-templates/strategy/index.ts create mode 100644 src/processor-templates/strategy/strategy.ts rename src/processors/{aave-compound => }/aave-compound.ts (92%) delete mode 100644 src/processors/aave-compound/index.ts delete mode 100644 src/processors/curve-lp/index.ts delete mode 100644 src/processors/dripper/index.ts delete mode 100644 src/processors/frax-staking/index.ts delete mode 100644 src/processors/morpho-aave/index.ts rename src/processors/{strategies => oeth}/balancer-meta-pool.ts (100%) rename src/processors/{curve-lp => oeth}/curve-lp.ts (100%) rename src/processors/{dripper => oeth}/dripper.ts (100%) rename src/processors/{frax-staking => oeth}/frax-staking.ts (100%) rename src/processors/{morpho-aave => oeth}/morpho-aave.ts (100%) rename src/processors/{vault => oeth}/vault.ts (100%) create mode 100644 src/processors/strategies/index.ts create mode 100644 src/processors/strategies/strategies.ts delete mode 100644 src/processors/vault/index.ts create mode 100644 src/utils/range.ts diff --git a/README.md b/README.md index 24c44007..4796c0bc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Origin Subsquid +### [Design Decisions Notion Document](https://www.notion.so/originprotocol/Subsquid-Design-Decisions-04ef82ae0d6848d1b14de893e9929ce4#d8e8d367069c4a619809e926f72db074) + ## Quickstart ```bash diff --git a/abi/initializable-abstract-strategy.json b/abi/initializable-abstract-strategy.json new file mode 100644 index 00000000..ce372861 --- /dev/null +++ b/abi/initializable-abstract-strategy.json @@ -0,0 +1,532 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "GovernorshipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_oldHarvesterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_newHarvesterAddress", + "type": "address" + } + ], + "name": "HarvesterAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "PTokenRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "PendingGovernorshipTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "_oldAddresses", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "_newAddresses", + "type": "address[]" + } + ], + "name": "RewardTokenAddressesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardTokenCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_pToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetToPToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "checkBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "collectRewardTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getRewardTokenAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "harvesterAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_rewardTokenAddresses", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_assets", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_pTokens", + "type": "address[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "platformAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_assetIndex", + "type": "uint256" + } + ], + "name": "removePToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "rewardTokenAddresses", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "safeApproveAllTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_harvesterAddress", + "type": "address" + } + ], + "name": "setHarvesterAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "address", + "name": "_pToken", + "type": "address" + } + ], + "name": "setPTokenAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_rewardTokenAddresses", + "type": "address[]" + } + ], + "name": "setRewardTokenAddresses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + } + ], + "name": "supportsAsset", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGovernor", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vaultAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/db/migrations/1697589427921-Data.js b/db/migrations/1697599559759-Data.js similarity index 97% rename from db/migrations/1697589427921-Data.js rename to db/migrations/1697599559759-Data.js index a5e2f4cc..be52a3c0 100644 --- a/db/migrations/1697589427921-Data.js +++ b/db/migrations/1697599559759-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1697589427921 { - name = 'Data1697589427921' +module.exports = class Data1697599559759 { + name = 'Data1697599559759' async up(db) { await db.query(`CREATE TABLE "exchange_rate" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "pair" text NOT NULL, "base" text NOT NULL, "quote" text NOT NULL, "rate" numeric NOT NULL, CONSTRAINT "PK_5c5d27d2b900ef6cdeef0398472" PRIMARY KEY ("id"))`) @@ -8,6 +8,9 @@ module.exports = class Data1697589427921 { await db.query(`CREATE TABLE "balance" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "token" text NOT NULL, "address" text NOT NULL, "balance" numeric NOT NULL, CONSTRAINT "PK_079dddd31a81672e8143a649ca0" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_a956b410c329b8eca7898c3c51" ON "balance" ("timestamp") `) await db.query(`CREATE INDEX "IDX_6b451b59c9f6a6fdd685f530b2" ON "balance" ("block_number") `) + await db.query(`CREATE TABLE "strategy_balance" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "strategy" text NOT NULL, "asset" text NOT NULL, "balance" numeric NOT NULL, CONSTRAINT "PK_ca6f93229d1392e9546d01dae4f" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_0113bf0b63183bea0d22cd0d08" ON "strategy_balance" ("timestamp") `) + await db.query(`CREATE INDEX "IDX_a88065dcd92011698bbe7df7b1" ON "strategy_balance" ("block_number") `) await db.query(`CREATE TABLE "curve_pool_balance" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "address" text NOT NULL, "balance0" numeric NOT NULL, "balance1" numeric NOT NULL, "balance2" numeric NOT NULL, CONSTRAINT "PK_40412750bb910ca560aa084dd88" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_ffb0d0f86f03faacef7cb3e092" ON "curve_pool_balance" ("timestamp") `) await db.query(`CREATE INDEX "IDX_db5522c865eb8ed76fa7aeb4a8" ON "curve_pool_balance" ("block_number") `) @@ -127,6 +130,9 @@ module.exports = class Data1697589427921 { await db.query(`DROP TABLE "balance"`) await db.query(`DROP INDEX "public"."IDX_a956b410c329b8eca7898c3c51"`) await db.query(`DROP INDEX "public"."IDX_6b451b59c9f6a6fdd685f530b2"`) + await db.query(`DROP TABLE "strategy_balance"`) + await db.query(`DROP INDEX "public"."IDX_0113bf0b63183bea0d22cd0d08"`) + await db.query(`DROP INDEX "public"."IDX_a88065dcd92011698bbe7df7b1"`) await db.query(`DROP TABLE "curve_pool_balance"`) await db.query(`DROP INDEX "public"."IDX_ffb0d0f86f03faacef7cb3e092"`) await db.query(`DROP INDEX "public"."IDX_db5522c865eb8ed76fa7aeb4a8"`) diff --git a/schema-base.graphql b/schema-base.graphql index b3da7a6d..8b650327 100644 --- a/schema-base.graphql +++ b/schema-base.graphql @@ -40,3 +40,14 @@ enum HistoryType { Yield } +type StrategyBalance @entity { + """ + Format: 'strategy:asset:blockNumber' + """ + id: ID! + timestamp: DateTime! @index + blockNumber: Int! @index + strategy: String! + asset: String! + balance: BigInt! +} \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 1878b3de..a8694809 100644 --- a/schema.graphql +++ b/schema.graphql @@ -42,7 +42,17 @@ enum HistoryType { Yield } -type CurvePoolBalance @entity { +type StrategyBalance @entity { + """ + Format: 'strategy:asset:blockNumber' + """ + id: ID! + timestamp: DateTime! @index + blockNumber: Int! @index + strategy: String! + asset: String! + balance: BigInt! +}type CurvePoolBalance @entity { id: ID! timestamp: DateTime! @index blockNumber: Int! @index diff --git a/src/abi/initializable-abstract-strategy.abi.ts b/src/abi/initializable-abstract-strategy.abi.ts new file mode 100644 index 00000000..48e5aa06 --- /dev/null +++ b/src/abi/initializable-abstract-strategy.abi.ts @@ -0,0 +1,513 @@ +export const ABI_JSON = [ + { + "type": "event", + "anonymous": false, + "name": "Deposit", + "inputs": [ + { + "type": "address", + "name": "_asset", + "indexed": true + }, + { + "type": "address", + "name": "_pToken", + "indexed": false + }, + { + "type": "uint256", + "name": "_amount", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "GovernorshipTransferred", + "inputs": [ + { + "type": "address", + "name": "previousGovernor", + "indexed": true + }, + { + "type": "address", + "name": "newGovernor", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "HarvesterAddressesUpdated", + "inputs": [ + { + "type": "address", + "name": "_oldHarvesterAddress", + "indexed": false + }, + { + "type": "address", + "name": "_newHarvesterAddress", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "PTokenAdded", + "inputs": [ + { + "type": "address", + "name": "_asset", + "indexed": true + }, + { + "type": "address", + "name": "_pToken", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "PTokenRemoved", + "inputs": [ + { + "type": "address", + "name": "_asset", + "indexed": true + }, + { + "type": "address", + "name": "_pToken", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "PendingGovernorshipTransfer", + "inputs": [ + { + "type": "address", + "name": "previousGovernor", + "indexed": true + }, + { + "type": "address", + "name": "newGovernor", + "indexed": true + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RewardTokenAddressesUpdated", + "inputs": [ + { + "type": "address[]", + "name": "_oldAddresses" + }, + { + "type": "address[]", + "name": "_newAddresses" + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "RewardTokenCollected", + "inputs": [ + { + "type": "address", + "name": "recipient", + "indexed": false + }, + { + "type": "address", + "name": "rewardToken", + "indexed": false + }, + { + "type": "uint256", + "name": "amount", + "indexed": false + } + ] + }, + { + "type": "event", + "anonymous": false, + "name": "Withdrawal", + "inputs": [ + { + "type": "address", + "name": "_asset", + "indexed": true + }, + { + "type": "address", + "name": "_pToken", + "indexed": false + }, + { + "type": "uint256", + "name": "_amount", + "indexed": false + } + ] + }, + { + "type": "function", + "name": "assetToPToken", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "checkBalance", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_asset" + } + ], + "outputs": [ + { + "type": "uint256", + "name": "balance" + } + ] + }, + { + "type": "function", + "name": "claimGovernance", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "collectRewardTokens", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "deposit", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_asset" + }, + { + "type": "uint256", + "name": "_amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "depositAll", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "getRewardTokenAddresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address[]", + "name": "" + } + ] + }, + { + "type": "function", + "name": "governor", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "harvesterAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "initialize", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address[]", + "name": "_rewardTokenAddresses" + }, + { + "type": "address[]", + "name": "_assets" + }, + { + "type": "address[]", + "name": "_pTokens" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "isGovernor", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "platformAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "removePToken", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "_assetIndex" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "rewardTokenAddresses", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "uint256", + "name": "" + } + ], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "safeApproveAllTokens", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + }, + { + "type": "function", + "name": "setHarvesterAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_harvesterAddress" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setPTokenAddress", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_asset" + }, + { + "type": "address", + "name": "_pToken" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "setRewardTokenAddresses", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address[]", + "name": "_rewardTokenAddresses" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "supportsAsset", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_asset" + } + ], + "outputs": [ + { + "type": "bool", + "name": "" + } + ] + }, + { + "type": "function", + "name": "transferGovernance", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_newGovernor" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "transferToken", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_asset" + }, + { + "type": "uint256", + "name": "_amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "vaultAddress", + "constant": true, + "stateMutability": "view", + "payable": false, + "inputs": [], + "outputs": [ + { + "type": "address", + "name": "" + } + ] + }, + { + "type": "function", + "name": "withdraw", + "constant": false, + "payable": false, + "inputs": [ + { + "type": "address", + "name": "_recipient" + }, + { + "type": "address", + "name": "_asset" + }, + { + "type": "uint256", + "name": "_amount" + } + ], + "outputs": [] + }, + { + "type": "function", + "name": "withdrawAll", + "constant": false, + "payable": false, + "inputs": [], + "outputs": [] + } +] diff --git a/src/abi/initializable-abstract-strategy.ts b/src/abi/initializable-abstract-strategy.ts new file mode 100644 index 00000000..12cfdcf0 --- /dev/null +++ b/src/abi/initializable-abstract-strategy.ts @@ -0,0 +1,153 @@ +import * as ethers from 'ethers' +import {LogEvent, Func, ContractBase} from './abi.support' +import {ABI_JSON} from './initializable-abstract-strategy.abi' + +export const abi = new ethers.Interface(ABI_JSON); + +export const events = { + Deposit: new LogEvent<([_asset: string, _pToken: string, _amount: bigint] & {_asset: string, _pToken: string, _amount: bigint})>( + abi, '0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62' + ), + GovernorshipTransferred: new LogEvent<([previousGovernor: string, newGovernor: string] & {previousGovernor: string, newGovernor: string})>( + abi, '0xc7c0c772add429241571afb3805861fb3cfa2af374534088b76cdb4325a87e9a' + ), + HarvesterAddressesUpdated: new LogEvent<([_oldHarvesterAddress: string, _newHarvesterAddress: string] & {_oldHarvesterAddress: string, _newHarvesterAddress: string})>( + abi, '0xe48386b84419f4d36e0f96c10cc3510b6fb1a33795620c5098b22472bbe90796' + ), + PTokenAdded: new LogEvent<([_asset: string, _pToken: string] & {_asset: string, _pToken: string})>( + abi, '0xef6485b84315f9b1483beffa32aae9a0596890395e3d7521f1c5fbb51790e765' + ), + PTokenRemoved: new LogEvent<([_asset: string, _pToken: string] & {_asset: string, _pToken: string})>( + abi, '0x16b7600acff27e39a8a96056b3d533045298de927507f5c1d97e4accde60488c' + ), + PendingGovernorshipTransfer: new LogEvent<([previousGovernor: string, newGovernor: string] & {previousGovernor: string, newGovernor: string})>( + abi, '0xa39cc5eb22d0f34d8beaefee8a3f17cc229c1a1d1ef87a5ad47313487b1c4f0d' + ), + RewardTokenAddressesUpdated: new LogEvent<([_oldAddresses: Array, _newAddresses: Array] & {_oldAddresses: Array, _newAddresses: Array})>( + abi, '0x04c0b9649497d316554306e53678d5f5f5dbc3a06f97dec13ff4cfe98b986bbc' + ), + RewardTokenCollected: new LogEvent<([recipient: string, rewardToken: string, amount: bigint] & {recipient: string, rewardToken: string, amount: bigint})>( + abi, '0xf6c07a063ed4e63808eb8da7112d46dbcd38de2b40a73dbcc9353c5a94c72353' + ), + Withdrawal: new LogEvent<([_asset: string, _pToken: string, _amount: bigint] & {_asset: string, _pToken: string, _amount: bigint})>( + abi, '0x2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b6398' + ), +} + +export const functions = { + assetToPToken: new Func<[_: string], {}, string>( + abi, '0x0fc3b4c4' + ), + checkBalance: new Func<[_asset: string], {_asset: string}, bigint>( + abi, '0x5f515226' + ), + claimGovernance: new Func<[], {}, []>( + abi, '0x5d36b190' + ), + collectRewardTokens: new Func<[], {}, []>( + abi, '0x5a063f63' + ), + deposit: new Func<[_asset: string, _amount: bigint], {_asset: string, _amount: bigint}, []>( + abi, '0x47e7ef24' + ), + depositAll: new Func<[], {}, []>( + abi, '0xde5f6268' + ), + getRewardTokenAddresses: new Func<[], {}, Array>( + abi, '0xf6ca71b0' + ), + governor: new Func<[], {}, string>( + abi, '0x0c340a24' + ), + harvesterAddress: new Func<[], {}, string>( + abi, '0x67c7066c' + ), + initialize: new Func<[_rewardTokenAddresses: Array, _assets: Array, _pTokens: Array], {_rewardTokenAddresses: Array, _assets: Array, _pTokens: Array}, []>( + abi, '0x435356d1' + ), + isGovernor: new Func<[], {}, boolean>( + abi, '0xc7af3352' + ), + platformAddress: new Func<[], {}, string>( + abi, '0xdbe55e56' + ), + removePToken: new Func<[_assetIndex: bigint], {_assetIndex: bigint}, []>( + abi, '0x9136616a' + ), + rewardTokenAddresses: new Func<[_: bigint], {}, string>( + abi, '0x7b2d9b2c' + ), + safeApproveAllTokens: new Func<[], {}, []>( + abi, '0xad1728cb' + ), + setHarvesterAddress: new Func<[_harvesterAddress: string], {_harvesterAddress: string}, []>( + abi, '0xc2e1e3f4' + ), + setPTokenAddress: new Func<[_asset: string, _pToken: string], {_asset: string, _pToken: string}, []>( + abi, '0x0ed57b3a' + ), + setRewardTokenAddresses: new Func<[_rewardTokenAddresses: Array], {_rewardTokenAddresses: Array}, []>( + abi, '0x96d538bb' + ), + supportsAsset: new Func<[_asset: string], {_asset: string}, boolean>( + abi, '0xaa388af6' + ), + transferGovernance: new Func<[_newGovernor: string], {_newGovernor: string}, []>( + abi, '0xd38bfff4' + ), + transferToken: new Func<[_asset: string, _amount: bigint], {_asset: string, _amount: bigint}, []>( + abi, '0x1072cbea' + ), + vaultAddress: new Func<[], {}, string>( + abi, '0x430bf08a' + ), + withdraw: new Func<[_recipient: string, _asset: string, _amount: bigint], {_recipient: string, _asset: string, _amount: bigint}, []>( + abi, '0xd9caed12' + ), + withdrawAll: new Func<[], {}, []>( + abi, '0x853828b6' + ), +} + +export class Contract extends ContractBase { + + assetToPToken(arg0: string): Promise { + return this.eth_call(functions.assetToPToken, [arg0]) + } + + checkBalance(_asset: string): Promise { + return this.eth_call(functions.checkBalance, [_asset]) + } + + getRewardTokenAddresses(): Promise> { + return this.eth_call(functions.getRewardTokenAddresses, []) + } + + governor(): Promise { + return this.eth_call(functions.governor, []) + } + + harvesterAddress(): Promise { + return this.eth_call(functions.harvesterAddress, []) + } + + isGovernor(): Promise { + return this.eth_call(functions.isGovernor, []) + } + + platformAddress(): Promise { + return this.eth_call(functions.platformAddress, []) + } + + rewardTokenAddresses(arg0: bigint): Promise { + return this.eth_call(functions.rewardTokenAddresses, [arg0]) + } + + supportsAsset(_asset: string): Promise { + return this.eth_call(functions.supportsAsset, [_asset]) + } + + vaultAddress(): Promise { + return this.eth_call(functions.vaultAddress, []) + } +} diff --git a/src/main.ts b/src/main.ts index b4f8ff6f..1b094997 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,19 +2,20 @@ import * as exchangeRates from './post-processors/exchange-rates' import { run } from './processor' import * as aaveCompound from './processors/aave-compound' import * as curve from './processors/curve' -import * as curveLp from './processors/curve-lp' -import * as dripper from './processors/dripper' -import * as fraxStaking from './processors/frax-staking' -import * as morphoAave from './processors/morpho-aave' import * as oeth from './processors/oeth' +import * as balancerMetaPoolStrategy from './processors/oeth/balancer-meta-pool' +import * as curveLp from './processors/oeth/curve-lp' +import * as dripper from './processors/oeth/dripper' +import * as fraxStaking from './processors/oeth/frax-staking' +import * as morphoAave from './processors/oeth/morpho-aave' +import * as vault from './processors/oeth/vault' import * as ousd from './processors/ousd' -import * as balancerMetaPoolStrategy from './processors/strategies/balancer-meta-pool' -import * as vault from './processors/vault' +import * as strategies from './processors/strategies' run([ { - name: 'grafana-metrics', - processors: [aaveCompound, curve], + name: 'block-frequency-updates', + processors: [aaveCompound, curve, strategies], }, { name: 'oeth', diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index 6e34eafa..ad6af785 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -1,5 +1,6 @@ export * from "./exchangeRate.model" export * from "./balance.model" +export * from "./strategyBalance.model" export * from "./curvePoolBalance.model" export * from "./oeth.model" export * from "./oethAddress.model" diff --git a/src/model/generated/strategyBalance.model.ts b/src/model/generated/strategyBalance.model.ts new file mode 100644 index 00000000..10ef4ea3 --- /dev/null +++ b/src/model/generated/strategyBalance.model.ts @@ -0,0 +1,32 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import * as marshal from "./marshal" + +@Entity_() +export class StrategyBalance { + constructor(props?: Partial) { + Object.assign(this, props) + } + + /** + * Format: 'strategy:asset:blockNumber' + */ + @PrimaryColumn_() + id!: string + + @Index_() + @Column_("timestamp with time zone", {nullable: false}) + timestamp!: Date + + @Index_() + @Column_("int4", {nullable: false}) + blockNumber!: number + + @Column_("text", {nullable: false}) + strategy!: string + + @Column_("text", {nullable: false}) + asset!: string + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + balance!: bigint +} diff --git a/src/processor-templates/curve/curve.ts b/src/processor-templates/curve/curve.ts index e24f4e5e..6b7dff1d 100644 --- a/src/processor-templates/curve/curve.ts +++ b/src/processor-templates/curve/curve.ts @@ -3,6 +3,7 @@ import { EvmBatchProcessor } from '@subsquid/evm-processor' import * as curveLpToken from '../../abi/curve-lp-token' import { CurvePoolBalance } from '../../model' import { Context } from '../../processor' +import { range } from '../../utils/range' interface ProcessResult { curvePoolBalances: CurvePoolBalance[] @@ -20,10 +21,13 @@ export const createCurveSetup = ( processor.includeAllBlocks({ from }) } -let lastBlockHeightProcessed = 0 -export const createCurveProcessor = - (poolAddress: string, count: number, from: number) => - async (ctx: Context) => { +export const createCurveProcessor = ( + poolAddress: string, + count: number, + from: number, +) => { + let lastBlockHeightProcessed = 0 + return async (ctx: Context) => { const result: ProcessResult = { curvePoolBalances: [], } @@ -38,9 +42,7 @@ export const createCurveProcessor = const contract = new curveLpToken.Contract(ctx, block.header, poolAddress) const balances = await Promise.all( - new Array(count) - .fill(0) - .map((_, index) => contract.balances(BigInt(index))), + range(count).map((n) => contract.balances(BigInt(n))), ) const curve = new CurvePoolBalance({ id: `${poolAddress}-${timestampId}`, @@ -56,3 +58,4 @@ export const createCurveProcessor = } await ctx.store.insert(result.curvePoolBalances) } +} diff --git a/src/processor-templates/erc20-balance/erc20-balance.ts b/src/processor-templates/erc20-balance/erc20-balance.ts index 8cb431a7..9e5b4355 100644 --- a/src/processor-templates/erc20-balance/erc20-balance.ts +++ b/src/processor-templates/erc20-balance/erc20-balance.ts @@ -13,20 +13,19 @@ export const createER20BalanceSetup = processor.includeAllBlocks({ from }) } -let lastBlockHeightProcessed = 0 -export const createERC20BalanceProcessor = - ({ - from, - address, - token, - frequency, - }: { - from: number - address: string - token: string - frequency: number - }) => - async (ctx: Context) => { +export const createERC20BalanceProcessor = ({ + from, + address, + token, + frequency, +}: { + from: number + address: string + token: string + frequency: number +}) => { + let lastBlockHeightProcessed = 0 + return async (ctx: Context) => { const result: ProcessResult = { balances: [], } @@ -51,3 +50,4 @@ export const createERC20BalanceProcessor = } await ctx.store.insert(result.balances) } +} diff --git a/src/processor-templates/otoken/otoken.ts b/src/processor-templates/otoken/otoken.ts index 7e51aae7..dabb4e17 100644 --- a/src/processor-templates/otoken/otoken.ts +++ b/src/processor-templates/otoken/otoken.ts @@ -58,6 +58,7 @@ export const createOTokenSetup = (processor: EvmBatchProcessor) => { processor.addTrace({ type: ['call'], + callTo: [address], callSighash: [ otoken.functions.rebaseOptOut.sighash, otoken.functions.rebaseOptIn.sighash, @@ -100,13 +101,16 @@ export const createOTokenProcessor = (params: { rebases: InstanceTypeOfConstructor[] rebaseOptions: InstanceTypeOfConstructor[] apies: InstanceTypeOfConstructor[] - owners: Map> lastYieldDistributionEvent?: { fee: bigint yield: bigint } } + let owners: + | Map> + | undefined = undefined + const process = async (ctx: Context) => { const result: ProcessResult = { initialized: false, @@ -114,7 +118,10 @@ export const createOTokenProcessor = (params: { initialize: async () => { if (result.initialized) return result.initialized = true - result.owners = await ctx.store + + // get all addresses from the database. + // we need this because we increase their balance based on rebase events + owners = await ctx.store .find>( params.OTokenAddress as any, ) @@ -125,12 +132,6 @@ export const createOTokenProcessor = (params: { rebases: [], rebaseOptions: [], apies: [], - // get all addresses from the database. - // we need this because we increase their balance based on rebase events - owners: undefined as unknown as Map< - string, - InstanceTypeOfConstructor - >, // We want to error if someone forgets to initialize. } for (const block of ctx.blocks) { @@ -144,14 +145,16 @@ export const createOTokenProcessor = (params: { } } - if (result.owners) { - await ctx.store.upsert([...result.owners.values()]) + if (owners) { + await ctx.store.upsert([...owners.values()]) } - await ctx.store.upsert(result.apies) - await ctx.store.insert(result.otokens) - await ctx.store.insert(result.history) - await ctx.store.insert(result.rebases) - await ctx.store.insert(result.rebaseOptions) + await Promise.all([ + ctx.store.upsert(result.apies), + ctx.store.insert(result.otokens), + ctx.store.insert(result.history), + ctx.store.insert(result.rebases), + ctx.store.insert(result.rebaseOptions), + ]) } const processTransfer = async ( @@ -184,16 +187,16 @@ export const createOTokenProcessor = (params: { params.OTOKEN_ADDRESS, ) // Transfer events - let addressSub = result.owners.get(data.from) - let addressAdd = result.owners.get(data.to) + let addressSub = owners!.get(data.from) + let addressAdd = owners!.get(data.to) if (addressSub == null) { addressSub = await createAddress(params.OTokenAddress, ctx, data.from) - result.owners.set(addressSub.id, addressSub) + owners!.set(addressSub.id, addressSub) } if (addressAdd == null) { addressAdd = await createAddress(params.OTokenAddress, ctx, data.to) - result.owners.set(addressAdd.id, addressAdd) + owners!.set(addressAdd.id, addressAdd) } addressSub.lastUpdated = new Date(block.header.timestamp) @@ -311,8 +314,11 @@ export const createOTokenProcessor = (params: { data, result.lastYieldDistributionEvent, ) - for (const address of result.owners.values()) { - if (address.rebasingOption === RebasingOption.OptOut) { + for (const address of owners!.values()) { + if ( + !address.credits || + address.rebasingOption === RebasingOption.OptOut + ) { continue } const newBalance = @@ -371,7 +377,7 @@ export const createOTokenProcessor = (params: { const blockNumber = block.header.height const address = trace.action.from.toLowerCase() const otokenObject = await getLatestOTokenObject(ctx, result, block) - let owner = result.owners.get(address) + let owner = owners!.get(address) if (!owner) { owner = await createAddress( params.OTokenAddress, @@ -379,7 +385,7 @@ export const createOTokenProcessor = (params: { address, timestamp, ) - result.owners.set(address, owner) + owners!.set(address, owner) } let rebaseOption = new params.OTokenRebaseOption({ diff --git a/src/processor-templates/strategy/index.ts b/src/processor-templates/strategy/index.ts new file mode 100644 index 00000000..31a0ec47 --- /dev/null +++ b/src/processor-templates/strategy/index.ts @@ -0,0 +1 @@ +export * from './strategy' diff --git a/src/processor-templates/strategy/strategy.ts b/src/processor-templates/strategy/strategy.ts new file mode 100644 index 00000000..99713669 --- /dev/null +++ b/src/processor-templates/strategy/strategy.ts @@ -0,0 +1,79 @@ +import { EvmBatchProcessor } from '@subsquid/evm-processor' + +import * as initializableAbstractStrategy from '../../abi/initializable-abstract-strategy' +import { StrategyBalance } from '../../model' +import { Context } from '../../processor' + +export const createStrategySetup = + (from: number) => (processor: EvmBatchProcessor) => { + processor.includeAllBlocks({ from }) + } + +export const createStrategyProcessor = ({ + from, + frequency, + address, + assets, +}: { + from: number + frequency: number + address: string + assets: readonly string[] +}) => { + let lastBlockHeightProcessed = 0 + let lastStrategyBalances = new Map() + return async (ctx: Context) => { + const results = { + strategyBalances: [] as StrategyBalance[], + } + const nextBlockIndex = ctx.blocks.findIndex( + (b) => b.header.height >= lastBlockHeightProcessed + frequency, + ) + for (let i = nextBlockIndex; i < ctx.blocks.length; i += frequency) { + const block = ctx.blocks[i] + if (!block || block.header.height < from) continue + + const contract = new initializableAbstractStrategy.Contract( + ctx, + block.header, + address, + ) + for (const asset of assets) { + const lastStrategyBalance = + lastStrategyBalances.get(asset) ?? + (await ctx.store.findOne(StrategyBalance, { + where: { strategy: address, asset }, + order: { + id: 'desc', + }, + })) + const balance = await contract.checkBalance(asset).catch((err) => { + if ( + err.message === 'execution reverted: aToken does not exist' || + err.message === 'execution reverted: pToken does not exist' || + err.message === 'execution reverted: Unsupported asset' || + err.message === 'execution reverted: Unexpected asset address' + ) { + return undefined + } + ctx.log.info({ address, asset }, 'retrieving strategy balance failed') + throw err + }) + if (balance === undefined) continue + if (!lastStrategyBalance || lastStrategyBalance.balance !== balance) { + const strategyBalance = new StrategyBalance({ + id: `${address}:${asset}:${block.header.height}`, + blockNumber: block.header.height, + timestamp: new Date(block.header.timestamp), + strategy: address, + asset, + balance, + }) + results.strategyBalances.push(strategyBalance) + lastStrategyBalances.set(asset, strategyBalance) + } + } + } + await ctx.store.insert(results.strategyBalances) + } +} diff --git a/src/processor.ts b/src/processor.ts index c26766b3..1cd050ec 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -74,6 +74,10 @@ export const run = ( }[], ) => { for (const { name, processors, postProcessors = [] } of params) { + if (process.env.PROCESSOR_NAME && process.env.PROCESSOR_NAME !== name) { + continue + } + const processor = createSquidProcessor() if (name) { // Hack our logging so it's unique per processor. diff --git a/src/processors/aave-compound/aave-compound.ts b/src/processors/aave-compound.ts similarity index 92% rename from src/processors/aave-compound/aave-compound.ts rename to src/processors/aave-compound.ts index 5433c35e..b82dbcc5 100644 --- a/src/processors/aave-compound/aave-compound.ts +++ b/src/processors/aave-compound.ts @@ -1,8 +1,8 @@ -import { Context } from '../../processor' +import { Context } from '../processor' import { createER20BalanceSetup, createERC20BalanceProcessor, -} from '../../processor-templates/erc20-balance' +} from '../processor-templates/erc20-balance' const ESTIMATED_BPS = 12.06 // Circa 2023 const SECONDS_PER_DAY = 86400 diff --git a/src/processors/aave-compound/index.ts b/src/processors/aave-compound/index.ts deleted file mode 100644 index 3fe7417c..00000000 --- a/src/processors/aave-compound/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './aave-compound' diff --git a/src/processors/curve-lp/index.ts b/src/processors/curve-lp/index.ts deleted file mode 100644 index 35918631..00000000 --- a/src/processors/curve-lp/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './curve-lp' diff --git a/src/processors/dripper/index.ts b/src/processors/dripper/index.ts deleted file mode 100644 index ef915095..00000000 --- a/src/processors/dripper/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dripper' diff --git a/src/processors/frax-staking/index.ts b/src/processors/frax-staking/index.ts deleted file mode 100644 index 4cec3a21..00000000 --- a/src/processors/frax-staking/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './frax-staking' diff --git a/src/processors/morpho-aave/index.ts b/src/processors/morpho-aave/index.ts deleted file mode 100644 index 253b5a6b..00000000 --- a/src/processors/morpho-aave/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './morpho-aave' diff --git a/src/processors/strategies/balancer-meta-pool.ts b/src/processors/oeth/balancer-meta-pool.ts similarity index 100% rename from src/processors/strategies/balancer-meta-pool.ts rename to src/processors/oeth/balancer-meta-pool.ts diff --git a/src/processors/curve-lp/curve-lp.ts b/src/processors/oeth/curve-lp.ts similarity index 100% rename from src/processors/curve-lp/curve-lp.ts rename to src/processors/oeth/curve-lp.ts diff --git a/src/processors/dripper/dripper.ts b/src/processors/oeth/dripper.ts similarity index 100% rename from src/processors/dripper/dripper.ts rename to src/processors/oeth/dripper.ts diff --git a/src/processors/frax-staking/frax-staking.ts b/src/processors/oeth/frax-staking.ts similarity index 100% rename from src/processors/frax-staking/frax-staking.ts rename to src/processors/oeth/frax-staking.ts diff --git a/src/processors/morpho-aave/morpho-aave.ts b/src/processors/oeth/morpho-aave.ts similarity index 100% rename from src/processors/morpho-aave/morpho-aave.ts rename to src/processors/oeth/morpho-aave.ts diff --git a/src/processors/vault/vault.ts b/src/processors/oeth/vault.ts similarity index 100% rename from src/processors/vault/vault.ts rename to src/processors/oeth/vault.ts diff --git a/src/processors/strategies/index.ts b/src/processors/strategies/index.ts new file mode 100644 index 00000000..dc043c0f --- /dev/null +++ b/src/processors/strategies/index.ts @@ -0,0 +1 @@ +export * from './strategies' diff --git a/src/processors/strategies/strategies.ts b/src/processors/strategies/strategies.ts new file mode 100644 index 00000000..6370d5df --- /dev/null +++ b/src/processors/strategies/strategies.ts @@ -0,0 +1,118 @@ +import { EvmBatchProcessor } from '@subsquid/evm-processor' + +import { Context } from '../../processor' +import { + createStrategyProcessor, + createStrategySetup, +} from '../../processor-templates/strategy' + +const ESTIMATED_BPS = 12.06 // Circa 2023 +const SECONDS_PER_DAY = 86400 +const BLOCKS_PER_DAY = SECONDS_PER_DAY / ESTIMATED_BPS +const UPDATE_FREQUENCY = Math.floor(BLOCKS_PER_DAY) + +const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'.toLowerCase() +const USDT = '0xdac17f958d2ee523a2206206994597c13d831ec7'.toLowerCase() +const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'.toLowerCase() +const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'.toLowerCase() +const stETH = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'.toLowerCase() +const rETH = '0xae78736Cd615f374D3085123A210448E74Fc6393'.toLowerCase() +const frxETH = '0x5E8422345238F34275888049021821E8E08CAa1f'.toLowerCase() + +const ousdStrategies = [ + { + from: 14206832, // 13369326, Initial Deploy + name: 'OUSD Aave', + address: '0x5e3646a1db86993f73e6b74a57d8640b69f7e259'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + { + from: 15896478, + name: 'OUSD Convex OUSD+3Crv (AMO)', + address: '0x89eb88fedc50fc77ae8a18aad1ca0ac27f777a90'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + { + from: 15949661, + name: 'OUSD Morpho Compound', + address: '0x5a4eee58744d1430876d5ca93cab5ccb763c037d'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + { + from: 16331911, + name: 'OUSD Morpho Aave', + address: '0x79F2188EF9350A1dC11A062cca0abE90684b0197'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + { + from: 17877308, + name: 'OUSD Flux', + address: '0x76Bf500B6305Dc4ea851384D3d5502f1C7a0ED44'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + { + from: 17883036, + name: 'OUSD Maker DSR', + address: '0x6b69B755C629590eD59618A2712d8a2957CA98FC'.toLowerCase(), + assets: [DAI, USDT, USDC], + }, + // Deprecated + // { + // from: 13369299, + // name: 'CompoundStrategy', + // address: '0x9c459eeb3fa179a40329b81c1635525e9a0ef094'.toLowerCase(), + // }, + // { + // from: 13639477, + // name: 'ConvexStrategy', + // address: '0xea2ef2e2e5a749d4a66b41db9ad85a38aa264cb3'.toLowerCase(), + // }, + // { + // from: 16226229, + // name: 'LUSDMetaStrategy', + // address: '0x7A192DD9Cc4Ea9bdEdeC9992df74F1DA55e60a19'.toLowerCase(), + // }, +] as const + +const oethStrategies = [ + { + from: 18083920, + name: 'OETH Convex ETH+OETH (AMO)', + address: '0x1827F9eA98E0bf96550b2FC20F7233277FcD7E63'.toLowerCase(), + assets: [WETH, stETH, rETH, frxETH], + }, + { + from: 17513633, + name: 'OETH Frax Staking', + address: '0x3fF8654D633D4Ea0faE24c52Aec73B4A20D0d0e5'.toLowerCase(), + assets: [WETH, stETH, rETH, frxETH], + }, + { + from: 17612333, + name: 'OETH Morpho Aave V2', + address: '0xc1fc9E5eC3058921eA5025D703CBE31764756319'.toLowerCase(), + assets: [WETH, stETH, rETH, frxETH], + }, + { + from: 18156225, + name: 'OETH Aura rETH/WETH', + address: '0x49109629aC1deB03F2e9b2fe2aC4a623E0e7dfDC'.toLowerCase(), + assets: [WETH, stETH, rETH, frxETH], + }, +] as const + +const strategies = [...ousdStrategies, ...oethStrategies] + +export const from = Math.min(...strategies.map((s) => s.from)) + +export const setup = (processor: EvmBatchProcessor) => { + strategies.forEach((s) => createStrategySetup(s.from)(processor)) +} + +const processors = strategies.map((s) => + createStrategyProcessor({ ...s, frequency: UPDATE_FREQUENCY }), +) + +export const process = async (ctx: Context) => { + await Promise.all(processors.map((p) => p(ctx))) +} diff --git a/src/processors/vault/index.ts b/src/processors/vault/index.ts deleted file mode 100644 index 5265e039..00000000 --- a/src/processors/vault/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './vault' diff --git a/src/utils/range.ts b/src/utils/range.ts new file mode 100644 index 00000000..4b5ea95c --- /dev/null +++ b/src/utils/range.ts @@ -0,0 +1,3 @@ +export const range = (count: number): number[] => { + return Array.from({ length: count }, (_, index) => index) +} From 84f72eb0b2940b560b6160ec13f41be2b74f0394 Mon Sep 17 00:00:00 2001 From: Chris Jacobs Date: Thu, 19 Oct 2023 10:43:46 -0700 Subject: [PATCH 4/4] fix: curve-lp processing bug --- src/processor-templates/curve/curve.ts | 1 + src/processors/oeth/curve-lp.ts | 131 ++++--------------------- 2 files changed, 22 insertions(+), 110 deletions(-) diff --git a/src/processor-templates/curve/curve.ts b/src/processor-templates/curve/curve.ts index 6b7dff1d..579b0e86 100644 --- a/src/processor-templates/curve/curve.ts +++ b/src/processor-templates/curve/curve.ts @@ -41,6 +41,7 @@ export const createCurveProcessor = ( const timestampId = timestamp.toISOString() const contract = new curveLpToken.Contract(ctx, block.header, poolAddress) + // TODO: use `get_balances()` where possible const balances = await Promise.all( range(count).map((n) => contract.balances(BigInt(n))), ) diff --git a/src/processors/oeth/curve-lp.ts b/src/processors/oeth/curve-lp.ts index ba17b3d1..5d0237ff 100644 --- a/src/processors/oeth/curve-lp.ts +++ b/src/processors/oeth/curve-lp.ts @@ -3,17 +3,14 @@ import { pad } from 'viem' import * as baseRewardPool from '../../abi/base-reward-pool' import * as curveLpToken from '../../abi/curve-lp-token' -import * as erc20 from '../../abi/erc20' import { OETHCurveLP } from '../../model' import { Context } from '../../processor' import { - OETH_ADDRESS, OETH_CONVEX_ADDRESS, OETH_CURVE_LP_ADDRESS, OETH_CURVE_REWARD_LP_ADDRESS, } from '../../utils/addresses' -import { getEthBalance } from '../../utils/getEthBalance' -import { getLatestEntity, trackAddressBalances } from '../utils' +import { getLatestEntity } from '../utils' interface ProcessResult { curveLPs: OETHCurveLP[] @@ -32,22 +29,10 @@ export const setup = (processor: EvmBatchProcessor) => { curveLpToken.events.RemoveLiquidity.topic, curveLpToken.events.RemoveLiquidityImbalance.topic, curveLpToken.events.RemoveLiquidityOne.topic, - // curve_lp_token.events.TokenExchange.topic, // Not sure if including this helps get up-to-date eth balances. + curveLpToken.events.TokenExchange.topic, ], range: { from }, }) - processor.addLog({ - address: [OETH_ADDRESS], - topic0: [erc20.events.Transfer.topic], - topic1: [pad(OETH_CURVE_LP_ADDRESS)], - range: { from }, - }) - processor.addLog({ - address: [OETH_ADDRESS], - topic0: [erc20.events.Transfer.topic], - topic2: [pad(OETH_CURVE_LP_ADDRESS)], - range: { from }, - }) processor.addLog({ address: [OETH_CURVE_REWARD_LP_ADDRESS], topic0: [ @@ -57,19 +42,6 @@ export const setup = (processor: EvmBatchProcessor) => { topic1: [pad(OETH_CONVEX_ADDRESS)], range: { from }, }) - // Not sure if this is needed to get up-to-date ETH balances. - // processor.addTransaction({ - // from: [OETH_CURVE_LP_ADDRESS], - // traces: false, - // logs: false, - // stateDiffs: false, - // }) - // processor.addTransaction({ - // to: [OETH_CURVE_LP_ADDRESS], - // traces: false, - // logs: false, - // stateDiffs: false, - // }) } export const process = async (ctx: Context) => { @@ -78,51 +50,40 @@ export const process = async (ctx: Context) => { } for (const block of ctx.blocks) { - let haveUpdatedEthBalance = false + const haveCurveLpEvent = block.logs.find( + (log) => log.address === OETH_CURVE_LP_ADDRESS, + ) + if (haveCurveLpEvent) { + await updateCurveValues(ctx, result, block) + } for (const log of block.logs) { - await processHoldingsTransfer(ctx, result, block, log) - if (log.address === OETH_CURVE_LP_ADDRESS) { - await processLiquidityEvents(ctx, result, block, log) - if (!haveUpdatedEthBalance) { - haveUpdatedEthBalance = true - await updateETHBalance(ctx, result, block) - } - } if (log.address === OETH_CURVE_REWARD_LP_ADDRESS) { await processCurveRewardEvents(ctx, result, block, log) } } - // Not sure if this is needed to get up-to-date ETH balances. - // const transaction = block.transactions.find( - // (transaction) => - // transaction.from === OETH_CURVE_LP_ADDRESS || - // transaction.to === OETH_CURVE_LP_ADDRESS, - // ) - // if (transaction) { - // await updateETHBalance(ctx, result, block, transaction.hash) - // } } await ctx.store.insert(result.curveLPs) } -const updateETHBalance = async ( +const updateCurveValues = async ( ctx: Context, result: ProcessResult, block: Context['blocks']['0'], ) => { - const [eth, { curveLP, isNew }] = await Promise.all([ - getEthBalance(ctx, OETH_CURVE_LP_ADDRESS, block), - getLatestCurveLP(ctx, result, block), + const { curveLP } = await getLatestCurveLP(ctx, result, block) + const poolContract = new curveLpToken.Contract( + ctx, + block.header, + OETH_CURVE_LP_ADDRESS, + ) + const [totalSupply, balances] = await Promise.all([ + poolContract.totalSupply(), + poolContract.get_balances(), ]) - if (curveLP.eth === eth) { - // No change, let's cancel what we're doing. - if (isNew) { - result.curveLPs.pop() - } - return - } - curveLP.eth = eth + curveLP.totalSupply = totalSupply + curveLP.eth = balances[0] + curveLP.oeth = balances[1] curveLP.ethOwned = curveLP.totalSupply ? (curveLP.eth * curveLP.totalSupplyOwned) / curveLP.totalSupply : 0n @@ -131,56 +92,6 @@ const updateETHBalance = async ( : 0n } -const processHoldingsTransfer = async ( - ctx: Context, - result: ProcessResult, - block: Context['blocks']['0'], - log: Context['blocks']['0']['logs']['0'], -) => { - if (log.topics[0] === erc20.events.Transfer.topic) { - await trackAddressBalances({ - log, - address: OETH_CURVE_LP_ADDRESS, - tokens: [OETH_ADDRESS], - fn: async ({ log, change }) => { - const { curveLP } = await getLatestCurveLP(ctx, result, block) - curveLP.oeth += change - curveLP.oethOwned = curveLP.totalSupply - ? (curveLP.oeth * curveLP.totalSupplyOwned) / curveLP.totalSupply - : 0n - }, - }) - } -} - -const processLiquidityEvents = async ( - ctx: Context, - result: ProcessResult, - block: Context['blocks']['0'], - log: Context['blocks']['0']['logs']['0'], -) => { - if (log.topics[0] === curveLpToken.events.AddLiquidity.topic) { - const { token_supply } = curveLpToken.events.AddLiquidity.decode(log) - const { curveLP } = await getLatestCurveLP(ctx, result, block) - curveLP.totalSupply = token_supply - } else if ( - log.topics[0] === curveLpToken.events.RemoveLiquidityImbalance.topic - ) { - const { token_supply } = - curveLpToken.events.RemoveLiquidityImbalance.decode(log) - const { curveLP } = await getLatestCurveLP(ctx, result, block) - curveLP.totalSupply = token_supply - } else if (log.topics[0] === curveLpToken.events.RemoveLiquidityOne.topic) { - const { token_supply } = curveLpToken.events.RemoveLiquidityOne.decode(log) - const { curveLP } = await getLatestCurveLP(ctx, result, block) - curveLP.totalSupply = token_supply - } else if (log.topics[0] === curveLpToken.events.RemoveLiquidity.topic) { - const { token_supply } = curveLpToken.events.RemoveLiquidity.decode(log) - const { curveLP } = await getLatestCurveLP(ctx, result, block) - curveLP.totalSupply = token_supply - } -} - const processCurveRewardEvents = async ( ctx: Context, result: ProcessResult,