From ac3b3efbcf91475ca8d442002139e1be4b60c979 Mon Sep 17 00:00:00 2001 From: jmulq Date: Thu, 7 Mar 2024 20:45:24 +0000 Subject: [PATCH] feat: add reward entity creation --- proto/curve/v1/curve.proto | 14 +- schema.graphql | 518 ++++++++++++++++----- src/constants.rs | 23 +- src/key_management/entity_key_manager.rs | 27 ++ src/key_management/store_key_manager.rs | 20 + src/modules/19_map_gauge_events.rs | 10 +- src/modules/1_map_curve_events.rs | 29 +- src/modules/21_store_reward_token_count.rs | 16 + src/modules/22_store_reward_tokens.rs | 65 +++ src/modules/420_graph_out.rs | 200 +++++++- src/modules/mod.rs | 10 +- src/pb/curve.types.v1.rs | 15 +- src/rpc/gauge.rs | 73 +++ src/rpc/mod.rs | 1 + src/rpc/pool.rs | 4 +- src/rpc/token.rs | 12 +- src/types/event_traits.rs | 15 +- src/types/gauge.rs | 11 +- substreams.yaml | 92 +++- 19 files changed, 974 insertions(+), 181 deletions(-) create mode 100644 src/modules/21_store_reward_token_count.rs create mode 100644 src/modules/22_store_reward_tokens.rs create mode 100644 src/rpc/gauge.rs diff --git a/proto/curve/v1/curve.proto b/proto/curve/v1/curve.proto index ccfaa59..aea0252 100755 --- a/proto/curve/v1/curve.proto +++ b/proto/curve/v1/curve.proto @@ -15,6 +15,7 @@ message Token { uint64 decimals = 4; string total_supply = 5; bool is_base_pool_lp_token = 6; + optional string gauge = 7; // Optional field to track the gauge for reward tokens } message Pool { @@ -67,12 +68,13 @@ message LiquidityEvent { message AddRewardEvent { string gauge = 1; - string reward_token = 2; - string distributor = 3; - string transaction_hash = 4; - uint32 tx_index = 5; - uint64 timestamp = 6; - uint64 block_number = 7; + string pool = 2; + string reward_token = 3; + string distributor = 4; + string transaction_hash = 5; + uint32 tx_index = 6; + uint64 timestamp = 7; + uint64 block_number = 8; } enum GaugeLiquidityEventType { diff --git a/schema.graphql b/schema.graphql index 44f67f7..30454bb 100644 --- a/schema.graphql +++ b/schema.graphql @@ -61,10 +61,6 @@ type Token @entity @regularPolling { " Total value locked in the protocol " _totalValueLockedUSD: BigDecimal! - - _largePriceChangeBuffer: Int! - - _largeTVLImpactBuffer: Int! } enum RewardTokenType { @@ -84,109 +80,21 @@ type RewardToken @entity @regularPolling { " The type of the reward token " type: RewardTokenType! + + " Links to intermediary entities representing the associations between this reward token and the liquidity pools it rewards " + pools: [PoolRewardToken!] @derivedFrom(field: "rewardToken") } -type LiquidityPool @entity @regularPolling { - " Smart contract address of the pool " +# Intermediary entity to represent the many-to-many relationship between LiquidityPools and RewardTokens +type PoolRewardToken @entity { + " { Pool address }-{ Reward Token address } " id: ID! - " The protocol this pool belongs to " - protocol: DexAmmProtocol! - - " Name of liquidity pool (e.g. Curve.fi DAI/USDC/USDT) " - name: String - - " Symbol of liquidity pool (e.g. 3CRV) " - symbol: String - - " Tokens that need to be deposited to take a position in protocol. e.g. WETH and USDC to deposit into the WETH-USDC pool. Array to account for multi-asset pools like Curve and Balancer " - inputTokens: [Token!]! - - " inputTokens in native (contract) order " - _inputTokensOrdered: [String!]! - - " Token that is minted to track ownership of position in protocol " - outputToken: Token - - " Aditional tokens that are given as reward for position in a protocol, usually in liquidity mining programs. e.g. SUSHI in the Onsen program, MATIC for Aave Polygon, usually in liquidity mining programs. e.g. SUSHI in the Onsen program, MATIC for Aave Polygon " - rewardTokens: [RewardToken!] - - " Fees per trade incurred to the user. Should include all fees that apply to a pool (e.g. Curve has a trading fee AND an admin fee, which is a portion of the trading fee. Uniswap only has a trading fee and no protocol fee. ) " - fees: [LiquidityPoolFee!]! - - " Whether this pool is single-sided (e.g. Bancor, Platypus's Alternative Pool). The specifics of the implementation depends on the protocol. " - isSingleSided: Boolean! - - " Creation timestamp " - createdTimestamp: BigInt! - - " Creation block number " - createdBlockNumber: BigInt! - - ##### Quantitative Data ##### - - " Current TVL (Total Value Locked) of this pool in USD " - totalValueLockedUSD: BigDecimal! - - " All revenue generated by the liquidity pool, accrued to the supply side. " - cumulativeSupplySideRevenueUSD: BigDecimal! - - " All revenue generated by the liquidity pool, accrued to the protocol. " - cumulativeProtocolSideRevenueUSD: BigDecimal! - - " All revenue generated by the liquidity pool. " - cumulativeTotalRevenueUSD: BigDecimal! - - " All historical trade volume occurred in this pool, in USD " - cumulativeVolumeUSD: BigDecimal! - - " Amount of input tokens in the pool. The ordering should be the same as the pool's `inputTokens` field. " - inputTokenBalances: [BigInt!]! - - " Weights of input tokens in the liquidity pool in percentage values. For example, 50/50 for Uniswap pools, 48.2/51.8 for a Curve pool, 10/10/80 for a Balancer pool " - inputTokenWeights: [BigDecimal!]! - - " Total supply of output token. Note that certain DEXes don't have an output token (e.g. Bancor) " - outputTokenSupply: BigInt - - " Price per share of output token in USD " - outputTokenPriceUSD: BigDecimal - - " Total supply of output tokens that are staked (usually in the MasterChef contract). Used to calculate reward APY. " - stakedOutputTokenAmount: BigInt - - " Per-block reward token emission as of the current block normalized to a day, in token's native amount. This should be ideally calculated as the theoretical rate instead of the realized amount. " - rewardTokenEmissionsAmount: [BigInt!] - - " Per-block reward token emission as of the current block normalized to a day, in USD value. This should be ideally calculated as the theoretical rate instead of the realized amount. " - rewardTokenEmissionsUSD: [BigDecimal!] - - ##### Snapshots ##### - - # " Liquidity pool daily snapshots " - # dailySnapshots: [LiquidityPoolDailySnapshot!]! @derivedFrom(field: "pool") - - # " Liquidity pool hourly snapshots " - # hourlySnapshots: [LiquidityPoolHourlySnapshot!]! @derivedFrom(field: "pool") - - ##### Events ##### - - # " All deposit (add liquidity) events occurred in this pool " - # deposits: [Deposit!]! @derivedFrom(field: "pool") - - # " All withdraw (remove liquidity) events occurred in this pool " - # withdraws: [Withdraw!]! @derivedFrom(field: "pool") - - # " All trade (swap) events occurred in this pool " - # swaps: [Swap!]! @derivedFrom(field: "pool") - - " Registry that deployed this pool " - _registryAddress: String! - - # " Liquidity Gauge associated with this pool " - # _gaugeAddress: String! + " Reference to the LiquidityPool entity this association belongs to " + pool: LiquidityPool! - _isMetapool: Boolean! + " Reference to the RewardToken entity associated with the LiquidityPool in this association " + rewardToken: RewardToken! } # Note that trading fee is the fee paid *by* the users, whereas LP fee and @@ -287,16 +195,16 @@ interface Protocol { ##### Snapshots ##### - # " Daily usage metrics for this protocol " - # dailyUsageMetrics: [UsageMetricsDailySnapshot!]! - # @derivedFrom(field: "protocol") + " Daily usage metrics for this protocol " + dailyUsageMetrics: [UsageMetricsDailySnapshot!]! + @derivedFrom(field: "protocol") - # " Hourly usage metrics for this protocol " - # hourlyUsageMetrics: [UsageMetricsHourlySnapshot!]! - # @derivedFrom(field: "protocol") + " Hourly usage metrics for this protocol " + hourlyUsageMetrics: [UsageMetricsHourlySnapshot!]! + @derivedFrom(field: "protocol") - # " Daily financial metrics for this protocol " - # financialMetrics: [FinancialsDailySnapshot!]! @derivedFrom(field: "protocol") + " Daily financial metrics for this protocol " + financialMetrics: [FinancialsDailySnapshot!]! @derivedFrom(field: "protocol") } type DexAmmProtocol implements Protocol @entity @regularPolling { @@ -352,25 +260,397 @@ type DexAmmProtocol implements Protocol @entity @regularPolling { ##### Snapshots ##### - # " Daily usage metrics for this protocol " - # dailyUsageMetrics: [UsageMetricsDailySnapshot!]! - # @derivedFrom(field: "protocol") + " Daily usage metrics for this protocol " + dailyUsageMetrics: [UsageMetricsDailySnapshot!]! + @derivedFrom(field: "protocol") - # " Hourly usage metrics for this protocol " - # hourlyUsageMetrics: [UsageMetricsHourlySnapshot!]! - # @derivedFrom(field: "protocol") + " Hourly usage metrics for this protocol " + hourlyUsageMetrics: [UsageMetricsHourlySnapshot!]! + @derivedFrom(field: "protocol") - # " Daily financial metrics for this protocol " - # financialMetrics: [FinancialsDailySnapshot!]! @derivedFrom(field: "protocol") + " Daily financial metrics for this protocol " + financialMetrics: [FinancialsDailySnapshot!]! @derivedFrom(field: "protocol") ##### Pools ##### " All pools that belong to this protocol " pools: [LiquidityPool!]! @derivedFrom(field: "protocol") +} + +############################### +##### Protocol Timeseries ##### +############################### + +type UsageMetricsDailySnapshot @entity @dailySnapshot { + " ID is # of days since Unix epoch time " + id: ID! + + " Protocol this snapshot is associated with " + protocol: DexAmmProtocol! + + " Number of unique daily active users " + dailyActiveUsers: Int! + + " Number of cumulative unique users " + cumulativeUniqueUsers: Int! + + " Total number of transactions occurred in a day. Transactions include all entities that implement the Event interface. " + dailyTransactionCount: Int! + + " Total number of deposits (add liquidity) in a day " + dailyDepositCount: Int! + + " Total number of withdrawals (remove liquidity) in a day " + dailyWithdrawCount: Int! + + " Total number of trades (swaps) in a day " + dailySwapCount: Int! + + " Total number of pools " + totalPoolCount: Int! + + " Block number of this snapshot " + blockNumber: BigInt! + + " Timestamp of this snapshot " + timestamp: BigInt! +} + +type UsageMetricsHourlySnapshot @entity @hourlySnapshot { + " { # of hours since Unix epoch time } " + id: ID! + + " Protocol this snapshot is associated with " + protocol: DexAmmProtocol! + + " Number of unique hourly active users " + hourlyActiveUsers: Int! + + " Number of cumulative unique users " + cumulativeUniqueUsers: Int! + + " Total number of transactions occurred in an hour. Transactions include all entities that implement the Event interface. " + hourlyTransactionCount: Int! + + " Total number of deposits (add liquidity) in an hour " + hourlyDepositCount: Int! + + " Total number of withdrawals (remove liquidity) in an hour " + hourlyWithdrawCount: Int! + + " Total number of trades (swaps) in an hour " + hourlySwapCount: Int! + + " Total number of pools " + totalPoolCount: Int! + + " Block number of this snapshot " + blockNumber: BigInt! + + " Timestamp of this snapshot " + timestamp: BigInt! +} + +type FinancialsDailySnapshot @entity @dailySnapshot { + " ID is # of days since Unix epoch time " + id: ID! + + " Protocol this snapshot is associated with " + protocol: DexAmmProtocol! + + " Current TVL (Total Value Locked) of the entire protocol " + totalValueLockedUSD: BigDecimal! + + " Current PCV (Protocol Controlled Value). Only relevant for protocols with PCV. " + protocolControlledValueUSD: BigDecimal + + " All trade volume occurred in a given day, in USD " + dailyVolumeUSD: BigDecimal! + + " All historical trade volume in USD " + cumulativeVolumeUSD: BigDecimal! + + " Revenue claimed by suppliers to the protocol. LPs on DEXs (e.g. 0.25% of the swap fee in Sushiswap). Depositors on Lending Protocols. NFT sellers on OpenSea. " + dailySupplySideRevenueUSD: BigDecimal! + + " Revenue claimed by suppliers to the protocol. LPs on DEXs (e.g. 0.25% of the swap fee in Sushiswap). Depositors on Lending Protocols. NFT sellers on OpenSea. " + cumulativeSupplySideRevenueUSD: BigDecimal! + + " Gross revenue for the protocol (revenue claimed by protocol). Examples: AMM protocol fee (Sushi’s 0.05%). OpenSea 10% sell fee. " + dailyProtocolSideRevenueUSD: BigDecimal! + + " Gross revenue for the protocol (revenue claimed by protocol). Examples: AMM protocol fee (Sushi’s 0.05%). OpenSea 10% sell fee. " + cumulativeProtocolSideRevenueUSD: BigDecimal! + + " All revenue generated by the protocol. e.g. 0.30% of swap fee in Sushiswap, all yield generated by Yearn. " + dailyTotalRevenueUSD: BigDecimal! + + " All revenue generated by the protocol. e.g. 0.30% of swap fee in Sushiswap, all yield generated by Yearn. " + cumulativeTotalRevenueUSD: BigDecimal! + + " Block number of this snapshot " + blockNumber: BigInt! + + " Timestamp of this snapshot " + timestamp: BigInt! +} + +############################### +##### Pool-Level Metadata ##### +############################### + +type LiquidityPool @entity @regularPolling { + " Smart contract address of the pool " + id: ID! + + " The protocol this pool belongs to " + protocol: DexAmmProtocol! + + " Name of liquidity pool (e.g. Curve.fi DAI/USDC/USDT) " + name: String + + " Symbol of liquidity pool (e.g. 3CRV) " + symbol: String + + " Tokens that need to be deposited to take a position in protocol. e.g. WETH and USDC to deposit into the WETH-USDC pool. Array to account for multi-asset pools like Curve and Balancer " + inputTokens: [Token!]! + + " inputTokens in native (contract) order " + _inputTokensOrdered: [String!]! + + " Token that is minted to track ownership of position in protocol " + outputToken: Token + + " Aditional tokens that are given as reward for position in a protocol, usually in liquidity mining programs. e.g. SUSHI in the Onsen program, MATIC for Aave Polygon, usually in liquidity mining programs. e.g. SUSHI in the Onsen program, MATIC for Aave Polygon " + rewardTokens: [PoolRewardToken!] @derivedFrom(field: "pool") + + " Fees per trade incurred to the user. Should include all fees that apply to a pool (e.g. Curve has a trading fee AND an admin fee, which is a portion of the trading fee. Uniswap only has a trading fee and no protocol fee. ) " + fees: [LiquidityPoolFee!]! + + " Whether this pool is single-sided (e.g. Bancor, Platypus's Alternative Pool). The specifics of the implementation depends on the protocol. " + isSingleSided: Boolean! + + " Creation timestamp " + createdTimestamp: BigInt! + + " Creation block number " + createdBlockNumber: BigInt! + + ##### Quantitative Data ##### + + " Current TVL (Total Value Locked) of this pool in USD " + totalValueLockedUSD: BigDecimal! + + " All revenue generated by the liquidity pool, accrued to the supply side. " + cumulativeSupplySideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool, accrued to the protocol. " + cumulativeProtocolSideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool. " + cumulativeTotalRevenueUSD: BigDecimal! + + " All historical trade volume occurred in this pool, in USD " + cumulativeVolumeUSD: BigDecimal! + + " Amount of input tokens in the pool. The ordering should be the same as the pool's `inputTokens` field. " + inputTokenBalances: [BigInt!]! + + " Weights of input tokens in the liquidity pool in percentage values. For example, 50/50 for Uniswap pools, 48.2/51.8 for a Curve pool, 10/10/80 for a Balancer pool " + inputTokenWeights: [BigDecimal!]! + + " Total supply of output token. Note that certain DEXes don't have an output token (e.g. Bancor) " + outputTokenSupply: BigInt + + " Price per share of output token in USD " + outputTokenPriceUSD: BigDecimal + + " Total supply of output tokens that are staked (usually in the MasterChef contract). Used to calculate reward APY. " + stakedOutputTokenAmount: BigInt + + " Per-block reward token emission as of the current block normalized to a day, in token's native amount. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsAmount: [BigInt!] + + " Per-block reward token emission as of the current block normalized to a day, in USD value. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsUSD: [BigDecimal!] + + #### Snapshots ##### + + " Liquidity pool daily snapshots " + dailySnapshots: [LiquidityPoolDailySnapshot!]! @derivedFrom(field: "pool") + + " Liquidity pool hourly snapshots " + hourlySnapshots: [LiquidityPoolHourlySnapshot!]! @derivedFrom(field: "pool") + + #### Events ##### + + " All deposit (add liquidity) events occurred in this pool " + deposits: [Deposit!]! @derivedFrom(field: "pool") + + " All withdraw (remove liquidity) events occurred in this pool " + withdraws: [Withdraw!]! @derivedFrom(field: "pool") + + " All trade (swap) events occurred in this pool " + swaps: [Swap!]! @derivedFrom(field: "pool") + + " Registry that deployed this pool " + _registryAddress: String! + + " Liquidity Gauge associated with this pool " + _gaugeAddress: String! + + _isMetapool: Boolean! +} + +################################# +##### Pool-Level Timeseries ##### +################################# + +type LiquidityPoolDailySnapshot @entity @dailySnapshot { + " { Smart contract address of the pool }-{ # of days since Unix epoch time } " + id: ID! + + " The protocol this snapshot belongs to " + protocol: DexAmmProtocol! + + " The pool this snapshot belongs to " + pool: LiquidityPool! + + " Block number of this snapshot " + blockNumber: BigInt! + + " Timestamp of this snapshot " + timestamp: BigInt! + + ##### Quantitative Data ##### + + " Current TVL (Total Value Locked) of this pool " + totalValueLockedUSD: BigDecimal! - _poolIds: [String!]! + " All revenue generated by the liquidity pool, accrued to the supply side. " + cumulativeSupplySideRevenueUSD: BigDecimal! + + " Daily revenue generated by the liquidity pool, accrued to the supply side. " + dailySupplySideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool, accrued to the protocol. " + cumulativeProtocolSideRevenueUSD: BigDecimal! + + " Daily revenue generated by the liquidity pool, accrued to the protocol. " + dailyProtocolSideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool. " + cumulativeTotalRevenueUSD: BigDecimal! + + " Daily revenue generated by the liquidity pool. " + dailyTotalRevenueUSD: BigDecimal! + + " All trade volume occurred in a given day, in USD " + dailyVolumeUSD: BigDecimal! + + " All trade volume occurred in a given day for a specific input token, in native amount. The ordering should be the same as the pool's `inputTokens` field. " + dailyVolumeByTokenAmount: [BigInt!]! + + " All trade volume occurred in a given day for a specific input token, in USD. The ordering should be the same as the pool's `inputTokens` field. " + dailyVolumeByTokenUSD: [BigDecimal!]! + + " All historical trade volume occurred in this pool, in USD " + cumulativeVolumeUSD: BigDecimal! + + " Amount of input tokens in the pool. The ordering should be the same as the pool's `inputTokens` field. " + inputTokenBalances: [BigInt!]! + + " Weights of input tokens in the liquidity pool in percentage values. For example, 50/50 for Uniswap pools, 48.2/51.8 for a Curve pool, 10/10/80 for a Balancer pool " + inputTokenWeights: [BigDecimal!]! + + " Total supply of output token. Note that certain DEXes don't have an output token (e.g. Bancor) " + outputTokenSupply: BigInt + + " Price per share of output token in USD " + outputTokenPriceUSD: BigDecimal + + " Total supply of output tokens that are staked (usually in the MasterChef contract). Used to calculate reward APY. " + stakedOutputTokenAmount: BigInt + + " Per-block reward token emission as of the current block normalized to a day, in token's native amount. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsAmount: [BigInt!] + + " Per-block reward token emission as of the current block normalized to a day, in USD value. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsUSD: [BigDecimal!] } +type LiquidityPoolHourlySnapshot @entity @hourlySnapshot { + " { Smart contract address of the pool }-{ # of hours since Unix epoch time } " + id: ID! + + " The protocol this snapshot belongs to " + protocol: DexAmmProtocol! + + " The pool this snapshot belongs to " + pool: LiquidityPool! + + " Block number of this snapshot " + blockNumber: BigInt! + + " Timestamp of this snapshot " + timestamp: BigInt! + + ##### Quantitative Data ##### + + " Current TVL (Total Value Locked) of this pool " + totalValueLockedUSD: BigDecimal! + + " All revenue generated by the liquidity pool, accrued to the supply side. " + cumulativeSupplySideRevenueUSD: BigDecimal! + + " Hourly revenue generated by the liquidity pool, accrued to the supply side. " + hourlySupplySideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool, accrued to the protocol. " + cumulativeProtocolSideRevenueUSD: BigDecimal! + + " Hourly revenue generated by the liquidity pool, accrued to the protocol. " + hourlyProtocolSideRevenueUSD: BigDecimal! + + " All revenue generated by the liquidity pool. " + cumulativeTotalRevenueUSD: BigDecimal! + + " Hourly revenue generated by the liquidity pool. " + hourlyTotalRevenueUSD: BigDecimal! + + " All trade volume occurred in a given hour, in USD " + hourlyVolumeUSD: BigDecimal! + + " All trade volume occurred in a given hour for a specific input token, in native amount. The ordering should be the same as the pool's `inputTokens` field. " + hourlyVolumeByTokenAmount: [BigInt!]! + + " All trade volume occurred in a given hour for a specific input token, in USD. The ordering should be the same as the pool's `inputTokens` field. " + hourlyVolumeByTokenUSD: [BigDecimal!]! + + " All historical trade volume occurred in this pool, in USD " + cumulativeVolumeUSD: BigDecimal! + + " Amount of input tokens in the pool. The ordering should be the same as the pool's `inputTokens` field. " + inputTokenBalances: [BigInt!]! + + " Weights of input tokens in the liquidity pool in percentage values. For example, 50/50 for Uniswap pools, 48.2/51.8 for a Curve pool, 10/10/80 for a Balancer pool " + inputTokenWeights: [BigDecimal!]! + + " Total supply of output token. Note that certain DEXes don't have an output token (e.g. Bancor) " + outputTokenSupply: BigInt + + " Price per share of output token in USD " + outputTokenPriceUSD: BigDecimal + + " Total supply of output tokens that are staked (usually in the MasterChef contract). Used to calculate reward APY. " + stakedOutputTokenAmount: BigInt + + " Per-block reward token emission as of the current block normalized to a day (not hour), in token's native amount. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsAmount: [BigInt!] + + " Per-block reward token emission as of the current block normalized to a day (not hour), in USD value. This should be ideally calculated as the theoretical rate instead of the realized amount. " + rewardTokenEmissionsUSD: [BigDecimal!] +} ################################## ##### Transaction-Level Data ##### diff --git a/src/constants.rs b/src/constants.rs index 3853fa7..d5c4e80 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,9 +1,15 @@ use hex_literal::hex; use lazy_static::lazy_static; use std::{collections::HashMap, ops::Div}; -use substreams::scalar::{BigDecimal, BigInt}; +use substreams::{ + scalar::{BigDecimal, BigInt}, + Hex, +}; -use crate::network_config::{PoolDetails, MISSING_OLD_POOLS_DATA}; +use crate::{ + network_config::{PoolDetails, CRV_TOKEN_ADDRESS, MISSING_OLD_POOLS_DATA}, + pb::curve::types::v1::Token, +}; // Chain Specific Contracts: // ________________________ @@ -123,6 +129,7 @@ pub fn one_usd_value() -> BigDecimal { pub const FEE_DENOMINATOR: u64 = 10000000000; pub const FEE_DECIMALS: u64 = 10; +pub const SECONDS_PER_DAY: u64 = 86400; pub fn default_pool_fee() -> BigInt { BigInt::from(4000000) @@ -131,3 +138,15 @@ pub fn default_pool_fee() -> BigInt { pub fn default_admin_fee() -> BigInt { BigInt::from(5000000000 as i64) } + +pub fn curve_token() -> Token { + Token { + address: Hex::encode(CRV_TOKEN_ADDRESS), + name: "Curve DAO Token".to_string(), + symbol: "CRV".to_string(), + decimals: 18, + total_supply: "0".to_string(), + is_base_pool_lp_token: false, + gauge: None, + } +} diff --git a/src/key_management/entity_key_manager.rs b/src/key_management/entity_key_manager.rs index 25e839e..fd5d887 100644 --- a/src/key_management/entity_key_manager.rs +++ b/src/key_management/entity_key_manager.rs @@ -10,6 +10,9 @@ pub enum EntityKey { LiquidityPoolDailySnapshot(String, i64), LiquidityPoolHourlySnapshot(String, i64), ProtocolDailyFinancialsSnapshot(i64), + Token(String), + RewardToken(String), + PoolRewardToken(String, String), Deposit(String, String), Swap(String, String), Withdraw(String, String), @@ -46,6 +49,19 @@ impl EntityKey { EntityKey::ProtocolDailyFinancialsSnapshot(*day_id).to_key_string() } + pub fn token_key(token_address: &str) -> String { + EntityKey::Token(token_address.to_string()).to_key_string() + } + + pub fn reward_token_key(reward_token_address: &str) -> String { + EntityKey::RewardToken(reward_token_address.to_string()).to_key_string() + } + + pub fn pool_reward_token_key(pool_address: &str, reward_token_address: &str) -> String { + EntityKey::PoolRewardToken(pool_address.to_string(), reward_token_address.to_string()) + .to_key_string() + } + pub fn deposit_key(transaction_hash: &str, log_index: &u32) -> String { EntityKey::Deposit(transaction_hash.to_string(), log_index.to_string()).to_key_string() } @@ -80,6 +96,17 @@ impl EntityKey { ) } EntityKey::ProtocolDailyFinancialsSnapshot(day_id) => day_id.to_string(), + EntityKey::Token(token_address) => format_address_string(token_address), + EntityKey::RewardToken(reward_token_address) => { + format_address_string(reward_token_address) + } + EntityKey::PoolRewardToken(pool_address, reward_token_address) => { + format!( + "{}-{}", + format_address_string(pool_address), + format_address_string(reward_token_address) + ) + } EntityKey::Deposit(tx_hash, log_index) => { format!("deposit-0x{}-{}", tx_hash, log_index) } diff --git a/src/key_management/store_key_manager.rs b/src/key_management/store_key_manager.rs index b4986bf..7c6c14e 100644 --- a/src/key_management/store_key_manager.rs +++ b/src/key_management/store_key_manager.rs @@ -19,6 +19,8 @@ pub enum StoreKey { PoolTvl(String), PoolTokenTvl(String, String), LiquidityGauge(String), + LiquidityGaugeRewardToken(String, i64), + LiquidityGaugeRewardTokenCount(String), ControllerGaugeAdded(String), ProtocolPoolCount, ProtocolVolumeUsd, @@ -169,6 +171,14 @@ impl StoreKey { StoreKey::LiquidityGauge(gauge_address.to_string()).to_key_string() } + pub fn liquidity_gauge_reward_token_key(gauge_address: &str, token_index: &i64) -> String { + StoreKey::LiquidityGaugeRewardToken(gauge_address.to_string(), *token_index).to_key_string() + } + + pub fn liquidity_gauge_reward_token_count_key(gauge_address: &str) -> String { + StoreKey::LiquidityGaugeRewardTokenCount(gauge_address.to_string()).to_key_string() + } + pub fn controller_gauge_added_key(gauge_address: &str) -> String { StoreKey::ControllerGaugeAdded(gauge_address.to_string()).to_key_string() } @@ -353,6 +363,16 @@ impl StoreKey { StoreKey::LiquidityGauge(gauge_address) => { format!("LiquidityGauge:{}", gauge_address) } + StoreKey::LiquidityGaugeRewardToken(gauge_address, token_index) => { + format!( + "LiquidityGaugeRewardToken:{}:{}", + gauge_address, + token_index.to_string() + ) + } + StoreKey::LiquidityGaugeRewardTokenCount(gauge_address) => { + format!("LiquidityGaugeRewardTokenCount:{}", gauge_address) + } StoreKey::ControllerGaugeAdded(gauge) => format!("ControllerGaugeAdded:{}", gauge), StoreKey::ProtocolPoolCount => "ProtocolPoolCount".to_string(), StoreKey::ProtocolVolumeUsd => "ProtocolVolumeUsd".to_string(), diff --git a/src/modules/19_map_gauge_events.rs b/src/modules/19_map_gauge_events.rs index 4afd200..2ad6d03 100644 --- a/src/modules/19_map_gauge_events.rs +++ b/src/modules/19_map_gauge_events.rs @@ -145,23 +145,25 @@ fn handle_liquidity_event( fn handle_add_reward_event( trx: &TransactionTrace, blk: ð::Block, - gauge: &Vec, + gauge_address: &Vec, reward_token: &Vec, distributor: &Vec, seen_tx_hashes: &mut HashSet, unique_add_reward_events: &mut Vec, gauge_store: &StoreGetProto, ) { - let gauge_address = StoreKey::liquidity_gauge_key(&Hex::encode(gauge)); + // Shadowing as don't need the raw address after this call + let gauge_address = StoreKey::liquidity_gauge_key(&Hex::encode(gauge_address)); // Check if the gauge related to the add_reward call exists in the gauge store - if gauge_store.get_last(gauge_address).is_some() { + if let Some(gauge) = gauge_store.get_last(&gauge_address) { let tx_hash_str = Hex::encode(&trx.hash); if seen_tx_hashes.insert(tx_hash_str.clone()) { // If the transaction hash was successfully inserted (i.e., it's a new unique hash), add the event. unique_add_reward_events.push(AddRewardEvent { - gauge: Hex::encode(&gauge), + gauge: Hex::encode(&gauge_address), + pool: gauge.pool, reward_token: Hex::encode(&reward_token), distributor: Hex::encode(&distributor), transaction_hash: tx_hash_str, diff --git a/src/modules/1_map_curve_events.rs b/src/modules/1_map_curve_events.rs index 88aac90..7ae6168 100644 --- a/src/modules/1_map_curve_events.rs +++ b/src/modules/1_map_curve_events.rs @@ -110,7 +110,7 @@ fn add_missing_pool( pool: &PoolDetails, ) -> Result<(), Error> { let pool_address = pool.address.to_vec(); - let lp_token = match token::create_token(&pool.lp_token.to_vec(), &pool_address) { + let lp_token = match token::create_token(&pool.lp_token.to_vec(), &pool_address, None) { Ok(token) => token, Err(e) => { return Err(anyhow!("Error in `add_missing_pools`: {:?}", e)); @@ -163,7 +163,7 @@ fn map_crypto_pool_deployed_events( return None; } }; - let lp_token = match token::create_token(&event.token, &pool_address) { + let lp_token = match token::create_token(&event.token, &pool_address, None) { Ok(token) => token, Err(e) => { substreams::log::debug!( @@ -221,16 +221,17 @@ fn map_plain_pool_deployed_events token, - Err(e) => { - substreams::log::debug!( - "Error in `map_plain_pool_deployed_events`: {:?}", - e - ); - return None; - } - }; + let lp_token = + match token::create_token(&transfer.receiver, &transfer.receiver, None) { + Ok(token) => token, + Err(e) => { + substreams::log::debug!( + "Error in `map_plain_pool_deployed_events`: {:?}", + e + ); + return None; + } + }; let (input_tokens, input_tokens_ordered) = match get_and_sort_input_tokens(&transfer.receiver) { Ok(result) => result, @@ -284,7 +285,7 @@ fn map_meta_pool_deployed_events( return None; } }; - let lp_token = match token::create_token(&transfer.receiver, &transfer.receiver) { + let lp_token = match token::create_token(&transfer.receiver, &transfer.receiver, None) { Ok(token) => token, Err(e) => { substreams::log::debug!( @@ -332,7 +333,7 @@ fn map_tricrypto_pool_deployed_events( &mut blk .events::(&[&address]) .filter_map(|(event, log)| { - let lp_token = match token::create_token(&event.pool, &event.pool) { + let lp_token = match token::create_token(&event.pool, &event.pool, None) { Ok(token) => token, Err(e) => { substreams::log::debug!( diff --git a/src/modules/21_store_reward_token_count.rs b/src/modules/21_store_reward_token_count.rs new file mode 100644 index 0000000..9e0bb3a --- /dev/null +++ b/src/modules/21_store_reward_token_count.rs @@ -0,0 +1,16 @@ +use substreams::store::{StoreAdd, StoreAddInt64, StoreNew}; + +use crate::{ + key_management::store_key_manager::StoreKey, pb::curve::types::v1::LiquidityGaugeEvents, +}; + +#[substreams::handlers::store] +pub fn store_reward_token_count(events: LiquidityGaugeEvents, store: StoreAddInt64) { + for event in events.add_reward_events { + store.add( + 0, + StoreKey::liquidity_gauge_reward_token_count_key(&event.gauge), + 1, + ) + } +} diff --git a/src/modules/22_store_reward_tokens.rs b/src/modules/22_store_reward_tokens.rs new file mode 100644 index 0000000..35082be --- /dev/null +++ b/src/modules/22_store_reward_tokens.rs @@ -0,0 +1,65 @@ +use substreams::store::{ + DeltaInt64, Deltas, StoreGet, StoreGetProto, StoreNew, StoreSet, StoreSetProto, +}; + +use crate::{ + key_management::store_key_manager::StoreKey, + pb::curve::types::v1::{LiquidityGauge, LiquidityGaugeEvents, Pool, Token}, + rpc, +}; + +#[substreams::handlers::store] +pub fn store_reward_tokens( + events: LiquidityGaugeEvents, + gauges_store: StoreGetProto, + reward_token_count_deltas: Deltas, + pools_store: StoreGetProto, + output_store: StoreSetProto, +) { + for event in events.add_reward_events { + if let Some(gauge) = gauges_store.get_last(StoreKey::liquidity_gauge_key(&event.gauge)) { + if let Some(pool) = pools_store.get_last(StoreKey::pool_key(&gauge.pool)) { + if let Some(delta) = reward_token_count_deltas.deltas.iter().find(|d| { + d.key == StoreKey::liquidity_gauge_reward_token_count_key(&gauge.gauge) + }) { + let reward_token_count = delta.new_value; + let key = StoreKey::liquidity_gauge_reward_token_key( + &gauge.gauge, + &reward_token_count, + ); + // Check if the reward token data exists in the stored pool to avoid redundant RPC calls + if let Some(reward_token) = pool + .input_tokens + .iter() + .find(|t| t.address == event.reward_token) + { + output_store.set( + 0, + key, + &Token { + address: reward_token.address.to_string(), + name: reward_token.name.to_string(), + symbol: reward_token.symbol.to_string(), + decimals: reward_token.decimals, + total_supply: reward_token.total_supply.to_string(), + is_base_pool_lp_token: reward_token.is_base_pool_lp_token, + gauge: Some(gauge.gauge.to_string()), + }, + ) + } else { + match rpc::token::create_token( + &event.reward_token_vec(), + &pool.address_vec(), + Some(&gauge.gauge), + ) { + Ok(token) => output_store.set(0, key, &token), + Err(e) => { + substreams::log::debug!("Error in `store_reward_tokens`: {:?}", e); + } + }; + } + } + } + } + } +} diff --git a/src/modules/420_graph_out.rs b/src/modules/420_graph_out.rs index c51f740..db53350 100644 --- a/src/modules/420_graph_out.rs +++ b/src/modules/420_graph_out.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, str::FromStr}; use anyhow::anyhow; use substreams::{ @@ -10,6 +10,7 @@ use substreams::{ DeltaBigDecimal, DeltaInt64, DeltaProto, Deltas, StoreGet, StoreGetBigDecimal, StoreGetBigInt, StoreGetInt64, StoreGetProto, StoreGetString, }, + Hex, }; use substreams_entity_change::{pb::entity::EntityChanges, tables::Tables}; @@ -17,21 +18,23 @@ use crate::{ common::{ format::{self, format_address_string}, pool_utils::{get_input_token_balances, get_input_token_weights}, - utils, + prices, utils, }, - constants, + constants::{self, curve_token, default_decimals, SECONDS_PER_DAY}, key_management::{entity_key_manager::EntityKey, store_key_manager::StoreKey}, - network_config::DEFAULT_NETWORK, + network_config::{CRV_TOKEN_ADDRESS, DEFAULT_NETWORK}, pb::{ curve::types::v1::{ events::{ pool_event::{DepositEvent, SwapEvent, SwapUnderlyingEvent, Type, WithdrawEvent}, PoolEvent, }, - Events, Pool, PoolFee, PoolFees, CurveEvents, Token, + CurveEvents, Events, LiquidityGauge, LiquidityGaugeEvents, Pool, PoolFee, PoolFees, + Token, }, uniswap_pricing::v1::Erc20Price, }, + rpc, timeframe_management::snapshot::snapshot_utils::manage_timeframe_snapshots, }; @@ -61,6 +64,13 @@ pub fn graph_out( protocol_tvl_store: StoreGetBigDecimal, usage_metrics_store: StoreGetInt64, current_time_deltas: Deltas, + gauge_store: StoreGetProto, + gauge_controller_store: StoreGetInt64, + gauge_events: LiquidityGaugeEvents, + crv_inflation_store: StoreGetString, + reward_token_count_store: StoreGetInt64, + reward_token_count_deltas: Deltas, + reward_tokens_store: StoreGetProto, uniswap_prices: StoreGetProto, chainlink_prices: StoreGetBigDecimal, ) -> Result { @@ -78,6 +88,186 @@ pub fn graph_out( create_pool_token_entities(&mut tables, &pool, &tokens_store)?; } + for event in gauge_events.add_reward_events { + if let Some(delta) = reward_token_count_deltas + .deltas + .iter() + .find(|d| d.key == StoreKey::liquidity_gauge_reward_token_count_key(&event.gauge)) + { + let start_index = delta.old_value; + let end_index = delta.new_value; + + for index in start_index..end_index { + if let Some(reward_token) = reward_tokens_store.get_last( + StoreKey::liquidity_gauge_reward_token_key(&event.gauge, &(index + 1)), + ) { + let price_usd = prices::get_token_usd_price( + &reward_token, + &uniswap_prices, + &chainlink_prices, + ); + // Check if Token has already been created during a pool deployment + match tokens_store.get_last(StoreKey::token_key(&reward_token.address)) { + // If already exists, just update the lastPriceUSD + Some(_) => { + tables + .update_row("Token", EntityKey::token_key(&event.reward_token)) + .set("lastPriceUSD", price_usd); + } + // If does not exist, go ahead and create the Token entity + None => { + tables + .create_row("Token", EntityKey::token_key(&event.reward_token)) + .set("name", reward_token.name) + .set("symbol", reward_token.symbol) + .set("decimals", reward_token.decimals as i32) + .set("isBasePoolLpToken", reward_token.is_base_pool_lp_token) + .set("lastPriceUSD", price_usd) + .set( + "_totalSupply", + BigInt::from_str(&reward_token.total_supply) + .unwrap_or_else(|_| BigInt::zero()), + ) + .set("_totalValueLockedUSD", BigDecimal::zero()) + .set("_largePriceChangeBuffer", 0) + .set("_largeTVLImpactBuffer", 0); + } + } + // Create the new entities representing the added gauge reward token + tables + .create_row( + "RewardToken", + EntityKey::reward_token_key(&event.reward_token), + ) + .set("token", EntityKey::token_key(&event.reward_token)) + .set("type", "DEPOSIT"); + + tables + .create_row( + "PoolRewardToken", + EntityKey::pool_reward_token_key(&event.pool, &event.reward_token), + ) + .set("pool", EntityKey::liquidity_pool_key(&event.pool)) + .set( + "rewardToken", + EntityKey::reward_token_key(&event.reward_token), + ); + } + } + } + } + + // When a Gauge is added to the GaugeController, it is now eligible for CRV rewards + for event in events.controller_gauges { + if let Some(gauge) = gauge_store.get_last(StoreKey::liquidity_gauge_key(&event.gauge)) { + let crv_address = Hex::encode(CRV_TOKEN_ADDRESS); + + tables + .create_row("RewardToken", EntityKey::reward_token_key(&crv_address)) + .set("token", EntityKey::token_key(&crv_address)) + .set("type", "DEPOSIT"); + + tables + .create_row( + "PoolRewardToken", + EntityKey::pool_reward_token_key(&gauge.pool, &crv_address), + ) + .set("pool", EntityKey::liquidity_pool_key(&gauge.pool)) + .set("rewardToken", EntityKey::reward_token_key(&crv_address)); + } + } + + for event in gauge_events.liquidity_events { + let mut reward_token_emissions_native: Vec = Vec::new(); + let mut reward_token_emissions_usd: Vec = Vec::new(); + + if let Some(gauge) = gauge_store.get_last(StoreKey::liquidity_gauge_key(&event.gauge)) { + // Handle CRV Rewards - if it has been added to the `GaugeController`, it is eligible for CRV rewards + if let Some(_) = + gauge_controller_store.get_last(StoreKey::controller_gauge_added_key(&gauge.gauge)) + { + if let Some(crv_inflation) = + crv_inflation_store.get_last(StoreKey::crv_inflation_rate_key()) + { + let gauge_rel_weight = + rpc::gauge::get_gauge_relative_weight(&gauge.address_vec()); + + let crv_emissions_native = (BigDecimal::from_str(&crv_inflation) + .unwrap_or_else(|_| BigDecimal::zero()) + * gauge_rel_weight + * SECONDS_PER_DAY) + .to_bigint(); + reward_token_emissions_native.push(crv_emissions_native.clone()); + + let price_usd = prices::get_token_usd_price( + &curve_token(), + &uniswap_prices, + &chainlink_prices, + ); + let crv_emissions_usd = + crv_emissions_native.to_decimal(default_decimals()) * price_usd; + reward_token_emissions_usd.push(crv_emissions_usd); + } + } + + // Handle Permissionless Rewards + if let Some(count) = reward_token_count_store.get_last( + StoreKey::liquidity_gauge_reward_token_count_key(&gauge.gauge), + ) { + for index in 0..count { + if let Some(reward_token) = reward_tokens_store.get_last( + StoreKey::liquidity_gauge_reward_token_key(&gauge.gauge, &(index + 1)), + ) { + match rpc::gauge::get_reward_token_data( + &gauge.address_vec(), + &reward_token.address_vec(), + ) { + Some(reward_data) => { + if reward_data.period_finish.to_u64() as i64 + > clock.clone().timestamp.unwrap().seconds + { + // Calculate native token emissions + let token_emissions_native = reward_data.rate * SECONDS_PER_DAY; + reward_token_emissions_native + .push(token_emissions_native.clone()); + + let price_usd = prices::get_token_usd_price( + &reward_token, + &uniswap_prices, + &chainlink_prices, + ); + + let token_emissions_usd = token_emissions_native + .to_decimal(default_decimals()) + * price_usd; + + // Calculate USD emissions + reward_token_emissions_usd.push(token_emissions_usd); + } else { + reward_token_emissions_native.push(BigInt::zero()); + reward_token_emissions_usd.push(BigDecimal::zero()); + } + } + None => { + reward_token_emissions_native.push(BigInt::zero()); + reward_token_emissions_usd.push(BigDecimal::zero()); + } + } + } + } + } + } + + tables + .update_row("LiquidityPool", EntityKey::liquidity_pool_key(&event.pool)) + .set( + "stakedOutputTokenAmount", + BigInt::from_str(&event.working_supply).unwrap_or_else(|_| BigInt::zero()), + ) + .set("rewardTokenEmissionsAmount", reward_token_emissions_native) + .set("rewardTokenEmissionsUSD", reward_token_emissions_usd); + } + for delta in pool_count_deltas.deltas.iter().last() { tables .update_row("DexAmmProtocol", EntityKey::protocol_key()) diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 167ea2b..1d46233 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -61,6 +61,12 @@ mod map_gauge_events; #[path = "20_store_crv_inflation.rs"] mod store_crv_inflation; +#[path = "21_store_reward_token_count.rs"] +mod store_reward_token_count; + +#[path = "22_store_reward_tokens.rs"] +mod store_reward_tokens; + // TODO: Will decrement once we have added and finalised all the other modules. #[path = "420_graph_out.rs"] mod graph_out; @@ -71,6 +77,7 @@ pub use map_extract_pool_events::map_extract_pool_events; pub use map_gauge_events::map_gauge_events; pub use store_active_users::store_active_users; pub use store_controller_gauges::store_controller_gauges; +pub use store_crv_inflation::store_crv_inflation; pub use store_current_time::store_current_time; pub use store_gauges::store_gauges; pub use store_input_token_balances::store_input_token_balances; @@ -84,6 +91,7 @@ pub use store_pool_volume_usd::store_pool_volume_usd; pub use store_pools_created::store_pools_created; pub use store_protocol_tvl::store_protocol_tvl; pub use store_protocol_volume_usd::store_protocol_volume_usd; +pub use store_reward_token_count::store_reward_token_count; +pub use store_reward_tokens::store_reward_tokens; pub use store_tokens::store_tokens; -pub use store_crv_inflation::store_crv_inflation; pub use store_usage_metrics::store_usage_metrics; diff --git a/src/pb/curve.types.v1.rs b/src/pb/curve.types.v1.rs index 3fa682d..e6ac19b 100644 --- a/src/pb/curve.types.v1.rs +++ b/src/pb/curve.types.v1.rs @@ -20,6 +20,9 @@ pub struct Token { pub total_supply: ::prost::alloc::string::String, #[prost(bool, tag="6")] pub is_base_pool_lp_token: bool, + /// Optional field to track the gauge for reward tokens + #[prost(string, optional, tag="7")] + pub gauge: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -113,16 +116,18 @@ pub struct AddRewardEvent { #[prost(string, tag="1")] pub gauge: ::prost::alloc::string::String, #[prost(string, tag="2")] - pub reward_token: ::prost::alloc::string::String, + pub pool: ::prost::alloc::string::String, #[prost(string, tag="3")] - pub distributor: ::prost::alloc::string::String, + pub reward_token: ::prost::alloc::string::String, #[prost(string, tag="4")] + pub distributor: ::prost::alloc::string::String, + #[prost(string, tag="5")] pub transaction_hash: ::prost::alloc::string::String, - #[prost(uint32, tag="5")] + #[prost(uint32, tag="6")] pub tx_index: u32, - #[prost(uint64, tag="6")] - pub timestamp: u64, #[prost(uint64, tag="7")] + pub timestamp: u64, + #[prost(uint64, tag="8")] pub block_number: u64, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/src/rpc/gauge.rs b/src/rpc/gauge.rs new file mode 100644 index 0000000..c3f8097 --- /dev/null +++ b/src/rpc/gauge.rs @@ -0,0 +1,73 @@ +use substreams::{scalar::BigInt, Hex}; + +use crate::{ + abi::curve::{gauge_controller, gauges}, + network_config::GAUGE_CONTROLLER_ADDRESS, + types::gauge::RewardData, +}; + +pub fn get_gauge_relative_weight(gauge_address: &Vec) -> BigInt { + match (gauge_controller::functions::GaugeRelativeWeight1 { + addr: gauge_address.clone(), + } + .call(GAUGE_CONTROLLER_ADDRESS.to_vec())) + { + Some(weight) => weight, + None => { + substreams::log::debug!( + "Failed to get gauge {} relative weight from gauge controller", + Hex::encode(&gauge_address) + ); + BigInt::zero() + } + } +} + +pub fn get_reward_token_data( + gauge_address: &Vec, + token_address: &Vec, +) -> Option { + match (gauges::liquidity_gauge_v4::functions::RewardData { + arg0: token_address.clone(), + } + .call(gauge_address.clone())) + { + Some((token, distributor, period_finish, rate, last_update, integral)) => { + return Some(RewardData { + token, + distributor, + period_finish, + rate, + last_update, + integral, + }) + } + None => { + // V5 and V6 gauges have the same function ABI, so we just need to match against one + match (gauges::liquidity_gauge_v6::functions::RewardData { + arg0: token_address.clone(), + } + .call(gauge_address.clone())) + { + Some((token, distributor, period_finish, rate, last_update, integral)) => { + return Some(RewardData { + token, + distributor, + period_finish, + rate, + last_update, + integral, + }) + } + None => { + substreams::log::debug!( + "Cannot find reward data for token {} on gauge {}", + Hex::encode(token_address), + Hex::encode(gauge_address) + ); + None + } + } + } + } +} diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index fcb436c..0d0672e 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -1,4 +1,5 @@ pub mod common; +pub mod gauge; pub mod oracle; pub mod token; pub mod pool; diff --git a/src/rpc/pool.rs b/src/rpc/pool.rs index 0bddf88..e52cd91 100644 --- a/src/rpc/pool.rs +++ b/src/rpc/pool.rs @@ -72,7 +72,7 @@ pub fn get_pool_coins(pool_address: &Vec) -> Result, Error> { break; } - match create_token(&input_token, &pool_address) { + match create_token(&input_token, &pool_address, None) { Ok(token) => { tokens.push(token); } @@ -134,7 +134,7 @@ pub fn get_pool_underlying_coins(pool_address: &Vec) -> Result<[Vec; 8], } pub fn get_pool_fee_and_admin_fee(pool_address: &Vec) -> Result<(BigInt, BigInt), Error> { - let batch: RpcBatch = RpcBatch::new(); + let batch = RpcBatch::new(); let responses = batch .add(functions::Fee {}, pool_address.clone()) .add(functions::AdminFee {}, pool_address.clone()) diff --git a/src/rpc/token.rs b/src/rpc/token.rs index aefe57c..0a882a6 100644 --- a/src/rpc/token.rs +++ b/src/rpc/token.rs @@ -10,7 +10,15 @@ use crate::{ rpc::{common::decode_rpc_response, registry::is_main_registry_pool}, }; -pub fn create_token(token_address: &Vec, pool_address: &Vec) -> Result { +pub fn create_token( + token_address: &Vec, + pool_address: &Vec, + gauge_address: Option<&String>, +) -> Result { + // Clones the string if gauge_address is Some + // This indicates the creation of a reward token + let gauge = gauge_address.map(|g| g.clone()); + if token_address == constants::ETH_ADDRESS.as_ref() { let total_supply = match get_token_supply(&token_address) { Ok(total_supply) => total_supply, @@ -26,6 +34,7 @@ pub fn create_token(token_address: &Vec, pool_address: &Vec) -> Result, pool_address: &Vec) -> Result Vec { + Hex::decode(&self.reward_token).unwrap() + } +} diff --git a/src/types/gauge.rs b/src/types/gauge.rs index cfa88b2..35ec7ed 100644 --- a/src/types/gauge.rs +++ b/src/types/gauge.rs @@ -1,4 +1,4 @@ -use substreams::Hex; +use substreams::{scalar::BigInt, Hex}; use crate::pb::curve::types::v1::LiquidityGauge; @@ -7,3 +7,12 @@ impl LiquidityGauge { Hex::decode(&self.gauge).unwrap() } } + +pub struct RewardData { + pub token: Vec, + pub distributor: Vec, + pub period_finish: BigInt, + pub rate: BigInt, + pub last_update: BigInt, + pub integral: BigInt, +} diff --git a/substreams.yaml b/substreams.yaml index ded6da1..949aa8f 100755 --- a/substreams.yaml +++ b/substreams.yaml @@ -23,7 +23,7 @@ binaries: modules: - name: map_curve_events kind: map - initialBlock: 19162490 + initialBlock: 10647875 inputs: - source: sf.ethereum.type.v2.Block output: @@ -168,15 +168,63 @@ modules: - name: store_current_time kind: store - initialBlock: 19162490 + initialBlock: 10647875 updatePolicy: set valueType: int64 inputs: - source: sf.substreams.v1.Clock + - name: store_gauges + kind: store + updatePolicy: set + valueType: proto:curve.types.v1.LiquidityGauge + inputs: + - map: map_curve_events + + - name: store_controller_gauges + kind: store + updatePolicy: set_if_not_exists + valueType: int64 + inputs: + - map: map_curve_events + + - name: map_gauge_events + kind: map + initialBlock: 10647875 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_gauges + output: + type: proto:curve.types.v1.LiquidityGaugeEvents + + - name: store_crv_inflation + kind: store + updatePolicy: set + valueType: string + inputs: + - map: map_curve_events + + - name: store_reward_token_count + kind: store + updatePolicy: add + valueType: int64 + inputs: + - map: map_gauge_events + + - name: store_reward_tokens + kind: store + updatePolicy: set + valueType: proto:curve.types.v1.RewardToken + inputs: + - map: map_gauge_events + - store: store_gauges + - store: store_reward_token_count + mode: deltas + - store: store_pools_created + - name: graph_out kind: map - initialBlock: 19162490 + initialBlock: 10647875 inputs: - source: sf.substreams.v1.Clock - map: map_curve_events @@ -206,8 +254,14 @@ modules: - store: store_usage_metrics - store: store_current_time mode: deltas - - store: chainlink_prices:chainlink_price_store + - store: store_gauges + - store: store_controller_gauges + - map: map_gauge_events + - store: store_crv_inflation + - store: store_reward_token_count + - store: store_reward_tokens - store: uniswap_prices:store_uniswap_prices + - store: chainlink_prices:chainlink_price_store output: type: proto:sf.substreams.sink.entity.v1.EntityChanges @@ -216,18 +270,18 @@ network: mainnet networks: mainnet: initialBlock: - chainlink_prices:store_confirmed_feeds: 19162490 - chainlink_prices:get_chainlink_answers: 19162490 - chainlink_prices:chainlink_price_store: 19162490 - chainlink_prices:graph_out: 19162490 - uniswap_prices:map_pair_created_events: 19162490 - uniswap_prices:store_pair_created_events: 19162490 - uniswap_prices:map_weth_prices: 19162490 - uniswap_prices:store_weth_prices: 19162490 - uniswap_prices:map_uniswap_prices: 19162490 - uniswap_prices:store_uniswap_prices: 19162490 - uniswap_prices:warmup: 19162490 - uniswap_prices:chainlink_prices:chainlink_price_store: 19162490 - uniswap_prices:chainlink_prices:graph_out: 19162490 - uniswap_prices:chainlink_prices:get_chainlink_answers: 19162490 - uniswap_prices:chainlink_prices:store_confirmed_feeds: 19162490 + chainlink_prices:store_confirmed_feeds: 10647875 + chainlink_prices:get_chainlink_answers: 10647875 + chainlink_prices:chainlink_price_store: 10647875 + chainlink_prices:graph_out: 10647875 + uniswap_prices:map_pair_created_events: 10647875 + uniswap_prices:store_pair_created_events: 10647875 + uniswap_prices:map_weth_prices: 10647875 + uniswap_prices:store_weth_prices: 10647875 + uniswap_prices:map_uniswap_prices: 10647875 + uniswap_prices:store_uniswap_prices: 10647875 + uniswap_prices:warmup: 10647875 + uniswap_prices:chainlink_prices:chainlink_price_store: 10647875 + uniswap_prices:chainlink_prices:graph_out: 10647875 + uniswap_prices:chainlink_prices:get_chainlink_answers: 10647875 + uniswap_prices:chainlink_prices:store_confirmed_feeds: 10647875