From 95a8ad7672996e4497a344ae7f3d710ebcfe94bf Mon Sep 17 00:00:00 2001 From: Dhruv Chauhan Date: Fri, 24 May 2024 18:53:39 +0530 Subject: [PATCH] add subgraph logic --- subgraphs/mantle-staked-eth/README.md | 34 ++ .../mantle-staked-eth/abis/Tokens/ERC20.json | 222 +++++++++ .../abis/Tokens/ERC20NameBytes.json | 17 + .../abis/Tokens/ERC20SymbolBytes.json | 17 + .../abis/mantle-staked-eth/METH.json | 456 ++++++++++++++++++ .../configurations/configurations.ts | 6 +- .../configurations/interface.ts | 1 - .../configurations.json | 2 +- .../configurations.ts | 11 +- .../templates/mantle-staked-eth.template.yaml | 60 ++- subgraphs/mantle-staked-eth/schema.graphql | 9 + .../mantle-staked-eth/src/common/constants.ts | 93 +--- .../src/mappings/handlers.ts | 233 +++++++-- .../mantle-staked-eth/src/prices/README.md | 295 +++++++++++ .../prices/calculations/CalculationsCurve.ts | 47 ++ .../calculations/CalculationsSushiswap.ts | 47 ++ .../src/prices/common/constants.ts | 43 ++ .../src/prices/common/types.ts | 147 ++++++ .../src/prices/common/utils.ts | 181 +++++++ .../src/prices/config/arbitrum.ts | 146 ++++++ .../src/prices/config/aurora.ts | 133 +++++ .../src/prices/config/avalanche.ts | 140 ++++++ .../src/prices/config/bsc.ts | 130 +++++ .../src/prices/config/celo.ts | 132 +++++ .../src/prices/config/cronos.ts | 131 +++++ .../src/prices/config/fantom.ts | 145 ++++++ .../src/prices/config/fuse.ts | 132 +++++ .../src/prices/config/gnosis.ts | 134 +++++ .../src/prices/config/harmony.ts | 136 ++++++ .../src/prices/config/mainnet.ts | 293 +++++++++++ .../src/prices/config/moonbeam.ts | 133 +++++ .../src/prices/config/optimism.ts | 145 ++++++ .../src/prices/config/polygon.ts | 139 ++++++ .../src/prices/config/template.ts | 142 ++++++ .../mantle-staked-eth/src/prices/index.ts | 151 ++++++ .../src/prices/oracles/AaveOracle.ts | 44 ++ .../src/prices/oracles/ChainLinkFeed.ts | 53 ++ .../src/prices/oracles/YearnLensOracle.ts | 44 ++ .../src/prices/routers/CurveRouter.ts | 368 ++++++++++++++ .../src/prices/routers/UniswapForksRouter.ts | 293 +++++++++++ subgraphs/mantle-staked-eth/src/sdk/README.md | 25 + .../src/sdk/protocols/config.ts | 45 ++ .../src/sdk/protocols/generic/account.ts | 93 ++++ .../src/sdk/protocols/generic/index.ts | 71 +++ .../src/sdk/protocols/generic/pool.ts | 433 +++++++++++++++++ .../src/sdk/protocols/generic/poolSnapshot.ts | 154 ++++++ .../src/sdk/protocols/generic/protocol.ts | 259 ++++++++++ .../sdk/protocols/generic/protocolSnapshot.ts | 215 +++++++++ .../src/sdk/protocols/generic/tokens.ts | 81 ++++ .../mantle-staked-eth/src/sdk/util/arrays.ts | 103 ++++ .../src/sdk/util/constants.ts | 229 +++++++++ .../mantle-staked-eth/src/sdk/util/events.ts | 70 +++ .../mantle-staked-eth/src/sdk/util/numbers.ts | 61 +++ .../mantle-staked-eth/src/sdk/util/rewards.ts | 295 +++++++++++ 54 files changed, 7064 insertions(+), 155 deletions(-) create mode 100644 subgraphs/mantle-staked-eth/abis/Tokens/ERC20.json create mode 100644 subgraphs/mantle-staked-eth/abis/Tokens/ERC20NameBytes.json create mode 100644 subgraphs/mantle-staked-eth/abis/Tokens/ERC20SymbolBytes.json create mode 100644 subgraphs/mantle-staked-eth/abis/mantle-staked-eth/METH.json rename subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/{ => mantle-staked-eth-ethereum}/configurations.ts (54%) create mode 100644 subgraphs/mantle-staked-eth/src/prices/README.md create mode 100644 subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsCurve.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsSushiswap.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/common/constants.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/common/types.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/common/utils.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/arbitrum.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/aurora.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/avalanche.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/bsc.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/celo.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/cronos.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/fantom.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/fuse.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/gnosis.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/harmony.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/mainnet.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/moonbeam.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/optimism.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/polygon.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/config/template.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/index.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/oracles/AaveOracle.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/oracles/ChainLinkFeed.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/oracles/YearnLensOracle.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/routers/CurveRouter.ts create mode 100644 subgraphs/mantle-staked-eth/src/prices/routers/UniswapForksRouter.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/README.md create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/config.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/account.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/index.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/pool.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/poolSnapshot.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocol.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocolSnapshot.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/protocols/generic/tokens.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/util/arrays.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/util/constants.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/util/events.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/util/numbers.ts create mode 100644 subgraphs/mantle-staked-eth/src/sdk/util/rewards.ts diff --git a/subgraphs/mantle-staked-eth/README.md b/subgraphs/mantle-staked-eth/README.md index 006487699b..9c2a6f6d02 100644 --- a/subgraphs/mantle-staked-eth/README.md +++ b/subgraphs/mantle-staked-eth/README.md @@ -1 +1,35 @@ # Mantle Staked ETH Subgraph + +## Methodology v1.0.0 + +Mantle Liquid Staking Protocol (LSP) is a permissionless, non-custodial ETH liquid staking protocol deployed on Ethereum L1 and governed by Mantle. Mantle Staked Ether (mETH) serves as the value-accumulating receipt token. + +## Metrics + +### Usage and Transactions + +- Stake ETH to instantly receive mETH. +- Unstake mETH to reclaim the initially staked ETH and accumulated rewards, with delays synchronized with the estimated "Exit Queue" wait time, and the Unstake Lifecycle. + +### TVL + +TVL is the total value of ETH staked with the protocol. + +### Fees + +Protocol applies a 10% fee on a user’s staking rewards. This fee is split between protocol and the node operators. + +### Revenue + +Staking rewards on the total staked ETH make up the total revenue. +Of this 10% is kept by the protocol as fees (protocol side revenue), rest 90% is passed on to the stakers (supply side revenue). + +## Useful Links + +- Landing Page: https://mantle.xyz/meth +- Staking App: https://meth.mantle.xyz/stake +- Docs: https://docs.mantle.xyz/meth +- Contracts: + - Staking Contract: https://etherscan.io/address/0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f + - Returns Aggregator Contract: https://etherscan.io/address/0x1766be66fBb0a1883d41B4cfB0a533c5249D3b82#code + - mETH token address: https://etherscan.io/address/0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa diff --git a/subgraphs/mantle-staked-eth/abis/Tokens/ERC20.json b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20.json new file mode 100644 index 0000000000..405d6b3648 --- /dev/null +++ b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] diff --git a/subgraphs/mantle-staked-eth/abis/Tokens/ERC20NameBytes.json b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20NameBytes.json new file mode 100644 index 0000000000..2d3c877a8c --- /dev/null +++ b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20NameBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/subgraphs/mantle-staked-eth/abis/Tokens/ERC20SymbolBytes.json b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20SymbolBytes.json new file mode 100644 index 0000000000..a76d616366 --- /dev/null +++ b/subgraphs/mantle-staked-eth/abis/Tokens/ERC20SymbolBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/subgraphs/mantle-staked-eth/abis/mantle-staked-eth/METH.json b/subgraphs/mantle-staked-eth/abis/mantle-staked-eth/METH.json new file mode 100644 index 0000000000..ba6a1e3d11 --- /dev/null +++ b/subgraphs/mantle-staked-eth/abis/mantle-staked-eth/METH.json @@ -0,0 +1,456 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { "inputs": [], "name": "NotStakingContract", "type": "error" }, + { + "inputs": [], + "name": "NotUnstakeRequestsManagerContract", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" } + ], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "getRoleMember", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" } + ], + "name": "getRoleMemberCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "admin", "type": "address" }, + { + "internalType": "contract IStaking", + "name": "staking", + "type": "address" + }, + { + "internalType": "contract IUnstakeRequestsManager", + "name": "unstakeRequestsManager", + "type": "address" + } + ], + "internalType": "struct METH.Init", + "name": "init", + "type": "tuple" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "staker", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingContract", + "outputs": [ + { "internalType": "contract IStaking", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + ], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unstakeRequestsManagerContract", + "outputs": [ + { + "internalType": "contract IUnstakeRequestsManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/subgraphs/mantle-staked-eth/configurations/configurations/configurations.ts b/subgraphs/mantle-staked-eth/configurations/configurations/configurations.ts index 916c5b0162..01b1112c32 100644 --- a/subgraphs/mantle-staked-eth/configurations/configurations/configurations.ts +++ b/subgraphs/mantle-staked-eth/configurations/configurations/configurations.ts @@ -2,19 +2,19 @@ import { log } from "@graphprotocol/graph-ts"; import { Configurations } from "./interface"; import { Deploy } from "./deploy"; -import { MantleStakedEthConfigurations } from "../../protocols/mantle-staked-eth/config/deployments/configurations"; +import { MantleStakedEthMainnetConfigurations } from "../../protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations"; export function getNetworkConfigurations(deploy: i32): Configurations { switch (deploy) { case Deploy.MANTLE_STAKED_ETH_ETHEREUM: { - return new MantleStakedEthConfigurations(); + return new MantleStakedEthMainnetConfigurations(); } default: { log.critical( "No configurations found for deployment protocol/network", [] ); - return new MantleStakedEthConfigurations(); + return new MantleStakedEthMainnetConfigurations(); } } } diff --git a/subgraphs/mantle-staked-eth/configurations/configurations/interface.ts b/subgraphs/mantle-staked-eth/configurations/configurations/interface.ts index 472c81cde0..cea3ae5803 100644 --- a/subgraphs/mantle-staked-eth/configurations/configurations/interface.ts +++ b/subgraphs/mantle-staked-eth/configurations/configurations/interface.ts @@ -4,6 +4,5 @@ export interface Configurations { getNetwork(): string; getProtocolName(): string; getProtocolSlug(): string; - getFactoryAddress(): Address; getLSTAddress(): Address; } diff --git a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.json b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.json index 38f9493aef..f25b6b69f2 100644 --- a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.json +++ b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.json @@ -1,6 +1,6 @@ { "deployment": "MANTLE_STAKED_ETH_ETHEREUM", - "network": "ethereum", + "network": "mainnet", "file": "./src/mappings/handlers.ts", "staking": { "address": "0xe3cbd06d7dadb3f4e6557bab7edd924cd1489e8f", diff --git a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/configurations.ts b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.ts similarity index 54% rename from subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/configurations.ts rename to subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.ts index 2bd565ee09..47867663b0 100644 --- a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/configurations.ts +++ b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/deployments/mantle-staked-eth-ethereum/configurations.ts @@ -1,13 +1,13 @@ import { Address } from "@graphprotocol/graph-ts"; -import { Configurations } from "../../../../configurations/configurations/interface"; +import { Configurations } from "../../../../../configurations/configurations/interface"; import { - Network, PROTOCOL_NAME, PROTOCOL_SLUG, -} from "../../../../src/common/constants"; +} from "../../../../../src/common/constants"; +import { Network } from "../../../../../src/sdk/util/constants"; -export class MantleStakedEthConfigurations implements Configurations { +export class MantleStakedEthMainnetConfigurations implements Configurations { getNetwork(): string { return Network.MAINNET; } @@ -17,9 +17,6 @@ export class MantleStakedEthConfigurations implements Configurations { getProtocolSlug(): string { return PROTOCOL_SLUG; } - getFactoryAddress(): Address { - return Address.fromString("0xe3cbd06d7dadb3f4e6557bab7edd924cd1489e8f"); - } getLSTAddress(): Address { return Address.fromString("0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa"); } diff --git a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/templates/mantle-staked-eth.template.yaml b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/templates/mantle-staked-eth.template.yaml index ae943df083..7314560bf4 100644 --- a/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/templates/mantle-staked-eth.template.yaml +++ b/subgraphs/mantle-staked-eth/protocols/mantle-staked-eth/config/templates/mantle-staked-eth.template.yaml @@ -32,9 +32,18 @@ dataSources: abis: - name: Staking file: ./abis/mantle-staked-eth/Staking.json + - name: METH + file: ./abis/mantle-staked-eth/METH.json - name: ReturnsAggregator file: ./abis/mantle-staked-eth/ReturnsAggregator.json + - name: ERC20 + file: ./abis/Tokens/ERC20.json + - name: ERC20SymbolBytes + file: ./abis/Tokens/ERC20SymbolBytes.json + - name: ERC20NameBytes + file: ./abis/Tokens/ERC20NameBytes.json + ########################################### ############## Price Oracle ############### ########################################### @@ -43,30 +52,30 @@ dataSources: file: ./abis/Prices/ERC20.json # Curve Contracts - name: CurvePool - file: ./abis/prices/Curve/Pool.json + file: ./abis/Prices/Curve/Pool.json - name: CurveRegistry - file: ./abis/prices/Curve/Registry.json + file: ./abis/Prices/Curve/Registry.json - name: CalculationsCurve - file: ./abis/prices/Calculations/Curve.json + file: ./abis/Prices/Calculations/Curve.json # YearnLens Contracts - name: YearnLensContract - file: ./abis/prices/YearnLens.json + file: ./abis/Prices/YearnLens.json # Aave Oracle Contract - name: AaveOracleContract - file: ./abis/prices/AaveOracle.json + file: ./abis/Prices/AaveOracle.json # SushiSwap Contracts - name: CalculationsSushiSwap - file: ./abis/prices/Calculations/SushiSwap.json + file: ./abis/Prices/Calculations/SushiSwap.json # ChainLink Contracts - name: ChainLinkContract - file: ./abis/prices/ChainLink.json + file: ./abis/Prices/ChainLink.json # Uniswap Contracts - name: UniswapRouter - file: ./abis/prices/Uniswap/Router.json + file: ./abis/Prices/Uniswap/Router.json - name: UniswapFactory - file: ./abis/prices/Uniswap/Factory.json + file: ./abis/Prices/Uniswap/Factory.json - name: UniswapPair - file: ./abis/prices/Uniswap/Pair.json + file: ./abis/Prices/Uniswap/Pair.json eventHandlers: - event: Staked(indexed address,uint256,uint256) handler: handleStaked @@ -98,9 +107,18 @@ dataSources: abis: - name: Staking file: ./abis/mantle-staked-eth/Staking.json + - name: METH + file: ./abis/mantle-staked-eth/METH.json - name: ReturnsAggregator file: ./abis/mantle-staked-eth/ReturnsAggregator.json + - name: ERC20 + file: ./abis/Tokens/ERC20.json + - name: ERC20SymbolBytes + file: ./abis/Tokens/ERC20SymbolBytes.json + - name: ERC20NameBytes + file: ./abis/Tokens/ERC20NameBytes.json + ########################################### ############## Price Oracle ############### ########################################### @@ -109,33 +127,31 @@ dataSources: file: ./abis/Prices/ERC20.json # Curve Contracts - name: CurvePool - file: ./abis/prices/Curve/Pool.json + file: ./abis/Prices/Curve/Pool.json - name: CurveRegistry - file: ./abis/prices/Curve/Registry.json + file: ./abis/Prices/Curve/Registry.json - name: CalculationsCurve - file: ./abis/prices/Calculations/Curve.json + file: ./abis/Prices/Calculations/Curve.json # YearnLens Contracts - name: YearnLensContract - file: ./abis/prices/YearnLens.json + file: ./abis/Prices/YearnLens.json # Aave Oracle Contract - name: AaveOracleContract - file: ./abis/prices/AaveOracle.json + file: ./abis/Prices/AaveOracle.json # SushiSwap Contracts - name: CalculationsSushiSwap - file: ./abis/prices/Calculations/SushiSwap.json + file: ./abis/Prices/Calculations/SushiSwap.json # ChainLink Contracts - name: ChainLinkContract - file: ./abis/prices/ChainLink.json + file: ./abis/Prices/ChainLink.json # Uniswap Contracts - name: UniswapRouter - file: ./abis/prices/Uniswap/Router.json + file: ./abis/Prices/Uniswap/Router.json - name: UniswapFactory - file: ./abis/prices/Uniswap/Factory.json + file: ./abis/Prices/Uniswap/Factory.json - name: UniswapPair - file: ./abis/prices/Uniswap/Pair.json + file: ./abis/Prices/Uniswap/Pair.json eventHandlers: - - event: ProtocolConfigChanged(indexed bytes4,string,bytes) - handler: handleProtocolConfigChanged - event: FeesCollected(uint256) handler: handleFeesCollected file: {{{ file }}} diff --git a/subgraphs/mantle-staked-eth/schema.graphql b/subgraphs/mantle-staked-eth/schema.graphql index 7bf13856c2..13b649c52b 100644 --- a/subgraphs/mantle-staked-eth/schema.graphql +++ b/subgraphs/mantle-staked-eth/schema.graphql @@ -537,3 +537,12 @@ type _CircularBuffer @entity { " The current calculated number of blocks per day based on calculated block speed " blocksPerDay: BigDecimal! } + +type _UnstakeRequest @entity { + " { Address of the account }-{ nonce } " + id: Bytes! + account: Bytes! + nonce: BigInt! + amount: BigInt! + claimed: Boolean! +} diff --git a/subgraphs/mantle-staked-eth/src/common/constants.ts b/subgraphs/mantle-staked-eth/src/common/constants.ts index 3367b85a21..ea0ab0be94 100644 --- a/subgraphs/mantle-staked-eth/src/common/constants.ts +++ b/subgraphs/mantle-staked-eth/src/common/constants.ts @@ -1,93 +1,6 @@ -import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; - -//////////////////// -///// Versions ///// -//////////////////// +///////////////////////////// +///// Protocol Specific ///// +///////////////////////////// export const PROTOCOL_NAME = "Mantle Staked ETH"; export const PROTOCOL_SLUG = "mantle-staked-eth"; - -//////////////////////// -///// Schema Enums ///// -//////////////////////// - -// The network names corresponding to the Network enum in the schema. -// They also correspond to the ones in `dataSource.network()` after converting to lower case. -// See below for a complete list: -// https://thegraph.com/docs/en/hosted-service/what-is-hosted-service/#supported-networks-on-the-hosted-service -export namespace Network { - export const ARBITRUM_ONE = "ARBITRUM_ONE"; - export const ARWEAVE_MAINNET = "ARWEAVE_MAINNET"; - export const AURORA = "AURORA"; - export const AVALANCHE = "AVALANCHE"; - export const BASE = "BASE"; - export const BOBA = "BOBA"; - export const BSC = "BSC"; // aka BNB Chain - export const CELO = "CELO"; - export const COSMOS = "COSMOS"; - export const CRONOS = "CRONOS"; - export const MAINNET = "MAINNET"; // Ethereum mainnet - export const FANTOM = "FANTOM"; - export const FUSE = "FUSE"; - export const HARMONY = "HARMONY"; - export const JUNO = "JUNO"; - export const MOONBEAM = "MOONBEAM"; - export const MOONRIVER = "MOONRIVER"; - export const NEAR_MAINNET = "NEAR_MAINNET"; - export const OPTIMISM = "OPTIMISM"; - export const OSMOSIS = "OSMOSIS"; - export const MATIC = "MATIC"; // aka Polygon - export const XDAI = "XDAI"; // aka Gnosis Chain -} - -export namespace ProtocolType { - export const EXCHANGE = "EXCHANGE"; - export const LENDING = "LENDING"; - export const YIELD = "YIELD"; - export const BRIDGE = "BRIDGE"; - export const GENERIC = "GENERIC"; -} - -//////////////////// -///// Ethereum ///// -//////////////////// - -export const ETH_NAME = "Ether"; -export const ETH_SYMBOL = "ETH"; -export const ETH_DECIMALS = 18; -export const ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; - -//////////////////////// -///// Type Helpers ///// -//////////////////////// - -export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; -export const BIGINT_ZERO = BigInt.fromI32(0); -export const BIGINT_ONE = BigInt.fromI32(1); -export const BIGINT_TWO = BigInt.fromI32(2); -export const BIGINT_TEN = BigInt.fromI32(10); -export const BIGINT_HUNDRED = BigInt.fromI32(100); -export const BIGINT_THOUSAND = BigInt.fromI32(1000); -export const BIGINT_TEN_TO_EIGHTEENTH = BigInt.fromString("10").pow(18); -export const BIGINT_MAX = BigInt.fromString( - "115792089237316195423570985008687907853269984665640564039457584007913129639935" -); - -export const INT_NEGATIVE_ONE = -1 as i32; -export const INT_ZERO = 0 as i32; -export const INT_ONE = 1 as i32; -export const INT_TWO = 2 as i32; -export const INT_FOUR = 4 as i32; - -export const BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); -export const BIGDECIMAL_ONE = new BigDecimal(BIGINT_ONE); -export const BIGDECIMAL_TWO = new BigDecimal(BIGINT_TWO); - -export const MAX_UINT = BigInt.fromI32(2).times(BigInt.fromI32(255)); - -export const SECONDS_PER_DAY = 60 * 60 * 24; -export const SECONDS_PER_HOUR = 60 * 60; - -//////////////////////// -/// Protocol Specific // -//////////////////////// diff --git a/subgraphs/mantle-staked-eth/src/mappings/handlers.ts b/subgraphs/mantle-staked-eth/src/mappings/handlers.ts index 3d6880b066..fde7f39446 100644 --- a/subgraphs/mantle-staked-eth/src/mappings/handlers.ts +++ b/subgraphs/mantle-staked-eth/src/mappings/handlers.ts @@ -1,4 +1,25 @@ -import { log } from "@graphprotocol/graph-ts"; +import { + Address, + BigDecimal, + BigInt, + Bytes, + log, +} from "@graphprotocol/graph-ts"; + +import { Versions } from "../versions"; +import { NetworkConfigs } from "../../configurations/configure"; +import { getUsdPrice, getUsdPricePerToken } from "../prices"; + +import { SDK } from "../sdk/protocols/generic"; +import { TokenInitializer, TokenParams } from "../sdk/protocols/generic/tokens"; +import { ProtocolConfig, TokenPricer } from "../sdk/protocols/config"; +import { bigIntToBigDecimal } from "../sdk/util/numbers"; +import { + BIGINT_MINUS_ONE, + BIGINT_TEN, + ETH_ADDRESS, + INT_ZERO, +} from "../sdk/util/constants"; import { Staked, @@ -6,52 +27,202 @@ import { UnstakeRequestClaimed, } from "../../generated/Staking/Staking"; import { - ProtocolConfigChanged, FeesCollected, + ReturnsAggregator, } from "../../generated/ReturnsAggregator/ReturnsAggregator"; +import { _ERC20 } from "../../generated/Staking/_ERC20"; +import { METH } from "../../generated/Staking/METH"; +import { Token, _UnstakeRequest } from "../../generated/schema"; + +const conf = new ProtocolConfig( + NetworkConfigs.getLSTAddress().toHexString(), + NetworkConfigs.getProtocolName(), + NetworkConfigs.getProtocolSlug(), + Versions +); + +class Pricer implements TokenPricer { + getTokenPrice(token: Token): BigDecimal { + const pricedToken = Address.fromBytes(token.id); + + return getUsdPricePerToken(pricedToken).usdPrice; + } + + getAmountValueUSD(token: Token, amount: BigInt): BigDecimal { + const pricedToken = Address.fromBytes(token.id); + const _amount = bigIntToBigDecimal(amount, token.decimals); + + return getUsdPrice(pricedToken, _amount); + } +} + +class TokenInit implements TokenInitializer { + getTokenParams(address: Address): TokenParams { + const erc20 = _ERC20.bind(address); + let name = "unknown"; + let symbol = "UNKNOWN"; + let decimals = INT_ZERO as i32; + + if (address == Address.fromString(ETH_ADDRESS)) { + name = "eth"; + symbol = "ETH"; + decimals = 18 as i32; + } else { + const nameCall = erc20.try_name(); + if (!nameCall.reverted) { + name = nameCall.value; + } else { + log.warning("[getTokenParams] nameCall reverted for {}", [ + address.toHexString(), + ]); + } + const symbolCall = erc20.try_symbol(); + if (!symbolCall.reverted) { + symbol = symbolCall.value; + } else { + log.warning("[getTokenParams] symbolCall reverted for {}", [ + address.toHexString(), + ]); + } + const decimalsCall = erc20.try_decimals(); + if (!decimalsCall.reverted) { + decimals = decimalsCall.value.toI32(); + } else { + log.warning("[getTokenParams] decimalsCall reverted for {}", [ + address.toHexString(), + ]); + } + } + const tokenParams = new TokenParams(name, symbol, decimals); + + return tokenParams; + } +} export function handleStaked(event: Staked): void { - log.debug("[Staked] staker: {} ethAmount: {} mETHAmount: {}", [ - event.params.staker.toHexString(), - event.params.ethAmount.toString(), - event.params.mETHAmount.toString(), - ]); + const staker = event.params.staker; + const ethAmount = event.params.ethAmount; + + const sdk = SDK.initializeFromEvent( + conf, + new Pricer(), + new TokenInit(), + event + ); + const token = sdk.Tokens.getOrCreateToken(Address.fromString(ETH_ADDRESS)); + const lst = sdk.Tokens.getOrCreateToken(NetworkConfigs.getLSTAddress()); + + const pool = sdk.Pools.loadPool(lst.id); + if (!pool.isInitialized) { + pool.initialize(lst.name, lst.symbol, [token.id], lst, true); + } + pool.addInputTokenBalances([ethAmount], true); + + const mETH = METH.bind(Address.fromBytes(lst.id)); + const supply = mETH.totalSupply(); + pool.setOutputTokenSupply(lst, supply); + + const account = sdk.Accounts.loadAccount(staker); + account.trackActivity(); } export function handleUnstakeRequested(event: UnstakeRequested): void { - log.debug( - "[UnstakeRequested] staker: {} ethAmount: {} mETHLocked: {} id: {}", - [ - event.params.staker.toHexString(), - event.params.ethAmount.toString(), - event.params.mETHLocked.toString(), - event.params.id.toString(), - ] + const staker = event.params.staker; + const ethAmount = event.params.ethAmount; + const nonce = event.params.id; + + const sdk = SDK.initializeFromEvent( + conf, + new Pricer(), + new TokenInit(), + event ); + + const account = sdk.Accounts.loadAccount(staker); + account.trackActivity(); + + const requestId = staker + .concat(Bytes.fromUTF8("-")) + .concat(Bytes.fromUTF8(nonce.toString())); + const unstakeRequest = new _UnstakeRequest(requestId); + unstakeRequest.account = staker; + unstakeRequest.nonce = nonce; + unstakeRequest.amount = ethAmount; + unstakeRequest.claimed = false; + unstakeRequest.save(); } export function handleUnstakeRequestClaimed( event: UnstakeRequestClaimed ): void { - log.debug("[UnstakeRequestClaimed] staker: {} id: {}", [ - event.params.staker.toHexString(), - event.params.id.toString(), - ]); -} + const staker = event.params.staker; + const nonce = event.params.id; -export function handleProtocolConfigChanged( - event: ProtocolConfigChanged -): void { - log.debug( - "[ProtocolConfigChanged] setterSelector: {} setterSignature: {} value: {}", - [ - event.params.setterSelector.toHexString(), - event.params.setterSignature, - event.params.value.toHexString(), - ] + const requestId = staker + .concat(Bytes.fromUTF8("-")) + .concat(Bytes.fromUTF8(nonce.toString())); + const unstakeRequest = _UnstakeRequest.load(requestId); + if (!unstakeRequest) { + log.warning( + "[UnstakeRequestClaimed] no unstake request found for staker: {} nonce: {}", + [staker.toHexString(), nonce.toString()] + ); + return; + } + + const ethAmount = unstakeRequest.amount; + unstakeRequest.claimed = true; + unstakeRequest.save(); + + const sdk = SDK.initializeFromEvent( + conf, + new Pricer(), + new TokenInit(), + event ); + const token = sdk.Tokens.getOrCreateToken(Address.fromString(ETH_ADDRESS)); + const lst = sdk.Tokens.getOrCreateToken(NetworkConfigs.getLSTAddress()); + + const pool = sdk.Pools.loadPool(lst.id); + if (!pool.isInitialized) { + pool.initialize(lst.name, lst.symbol, [token.id], lst, true); + } + pool.addInputTokenBalances([ethAmount.times(BIGINT_MINUS_ONE)], true); + + const mETH = METH.bind(Address.fromBytes(lst.id)); + const supply = mETH.totalSupply(); + pool.setOutputTokenSupply(lst, supply); } export function handleFeesCollected(event: FeesCollected): void { - log.debug("[FeesCollected] amount: {}", [event.params.amount.toString()]); + const protocolSideamount = event.params.amount; + + const contract = ReturnsAggregator.bind(event.address); + const feeBPSCall = contract.try_feesBasisPoints(); + if (feeBPSCall.reverted) { + log.warning( + "[FeesCollected] feesBasisPoints call failed on contract address: {}", + [event.address.toHexString()] + ); + return; + } + const feeBPS = feeBPSCall.value; + const supplySideAmount = protocolSideamount.times( + BigInt.fromI32(1 - feeBPS / 10000).times(BIGINT_TEN) + ); + + const sdk = SDK.initializeFromEvent( + conf, + new Pricer(), + new TokenInit(), + event + ); + const token = sdk.Tokens.getOrCreateToken(Address.fromString(ETH_ADDRESS)); + const lst = sdk.Tokens.getOrCreateToken(NetworkConfigs.getLSTAddress()); + + const pool = sdk.Pools.loadPool(lst.id); + if (!pool.isInitialized) { + pool.initialize(lst.name, lst.symbol, [token.id], lst, true); + } + pool.addRevenueNative(token, protocolSideamount, supplySideAmount); } diff --git a/subgraphs/mantle-staked-eth/src/prices/README.md b/subgraphs/mantle-staked-eth/src/prices/README.md new file mode 100644 index 0000000000..a21d78286a --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/README.md @@ -0,0 +1,295 @@ +# Price Oracle + +## Configuration + +In `subgraph.yaml`, add the following code snippet inside the `abis` section of the `datasources` which is going to fetch prices of token using the `Price Oracle`. +
+**NOTE**: Include the following code snippet in each of the datasources, that is dependent on the `Price Oracle` and update imports in each file inside oracle folder. + +``` +########################################### +############## Price Oracle ############### +########################################### +# ERC20 +- name: _ERC20 + file: ./abis/Prices/ERC20.json +# Curve Contracts +- name: CurvePool + file: ./abis/Prices/Curve/Pool.json +- name: CurveRegistry + file: ./abis/Prices/Curve/Registry.json +- name: CalculationsCurve + file: ./abis/Prices/Calculations/Curve.json +# YearnLens Contracts +- name: YearnLensContract + file: ./abis/Prices/YearnLens.json +# Aave Oracle Contract +- name: AaveOracleContract + file: ./abis/Prices/AaveOracle.json +# SushiSwap Contracts +- name: CalculationsSushiSwap + file: ./abis/Prices/Calculations/SushiSwap.json +# ChainLink Contracts +- name: ChainLinkContract + file: ./abis/Prices/ChainLink.json +# Uniswap Contracts +- name: UniswapRouter + file: ./abis/Prices/Uniswap/Router.json +- name: UniswapFactory + file: ./abis/Prices/Uniswap/Factory.json +- name: UniswapPair + file: ./abis/Prices/Uniswap/Pair.json +``` + +## Usage + +Following are some ways through which you can get the prices of tokens: + +``` +import { BigDecimal } from "@graphprotocol/graph-ts"; +import { getUsdPrice, getUsdPricePerToken, getLiquidityBoundPrice } from "../Oracle"; + +// Method 1 +// Using function getUsdPrice(tokenAddr: Address, amount: BigDecimal): BigDecimal + +let tokenPrice = getUsdPrice(tokenAddr, amount); +``` + +> Note: Preferred as it internally calls `getLiquidityBoundPrice(...)` and returns token price bounded by it's pool's liquidity. +>
+> To get the price per token, you can still use `getUsdPrice(...)` as: +>

> `let tokenPrice = getUsdPrice(tokenAddr, BIGDECIMAL_ONE);` + +``` +// Method 2 +// Using function getUsdPricePerToken(tokenAddr: Address): CustomPriceType + +let tokenPrice: BigDecimal; +let fetchPrice = getUsdPricePerToken(tokenAddr); + +// fetchPrice.reverted: Bool +// fetchPrice.usdPrice: BigDecimal +// fetchPrice.decimals: number +// fetchPrice.oracleType: string +// fetchPrice.liquidity: BigDecimal + +if (!fetchPrice.reverted) { + if ( + fetchPrice.oracleType == constants.OracleType.UNISWAP_FORKS_ROUTER || + fetchPrice.oracleType == constants.OracleType.CURVE_ROUTER + ) { + fetchPrice = getLiquidityBoundPrice(tokenAddr, fetchPrice, amount) + } + tokenPrice = fetchPrice.usdPrice * amount +} +``` + +## Optimizations + +- Configure default `number of oracles` to fetch price from and their `order of preference` in OracleType's constructor, depending on which oracles _generally_ works best for you. +- Although querying multiple oracles for the same token's price mean more contract calls, this overhead can be beared for smaller subgraphs, and for specific tokens/block ranges for larger ones in favour of spotting and ignoring outlier values by avoiding complete reliance on a single source of truth and trusting the closer majority values, especially for low/distributed liquidity tokens, or during volatile markets. +

+ The result is an average price on the k-closest reported values. +
+ where, `k = ceil(reportedPrices.length/2)` +- For any observed pricing discrepancies, you may define an override on the default oracle configuration in network's config file which works better for the mispriced token(s). +
+ An example override is defined under `ORACLE CONFIG OVERRIDES` section in `config/template.ts` +

+ Any new overrides shall be maintained in both the subgraph as well as the reference pricelib directory, so the same inconsistencies do not have to be handled separately. + +## Folder Structure + +``` +Prices +├── calculations +│ ├── CalculationsCurve.ts +│ └── CalculationsSushiSwap.ts +├── common +│ ├── types.ts +│ ├── constants.ts +│ └── utils.ts +├── config +│ ├── arbitrum.ts +│ ├── aurora.ts +│ ├── avalanche.ts +│ ├── bsc.ts +│ ├── celo.ts +│ ├── cronos.ts +│ ├── fantom.ts +│ ├── fuse.ts +│ ├── gnosis.ts +│ ├── harmony.ts +│ ├── mainnet.ts +│ ├── moonbeam.ts +│ ├── optimism.ts +│ ├── polygon.ts +│ └── template.ts +├── oracles +│ ├── AaveOracle.ts +│ ├── ChainLinkFeed.ts +│ └── YearnLensOracle.ts +├── routers +│ ├── CurveRouter.ts +│ └── UniswapForksRouter.ts +│── README.md +└── index.ts +``` + +## Development Status + +🔨 = In progress. +🛠 = Feature complete. Additional testing required. +`MultiCall` = If the method uses more than two `JSON RPC Call`. + +### Arbitrum + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| YearnLens | `0x043518ab266485dc085a1db095b8d9c2fc78e9b9` | `2396321` | ❎ | +| AaveOracle | `0xb56c2F0B653B2e0b10C9b928C8580Ac5Df02C7C7` | `7740843` | ❎ | +| Curve | `0x3268c3bda100ef0ff3c2d044f23eab62c80d78d2` | `11707234` | ❎ | +| SushiSwap | `0x5ea7e501c9a23f4a76dc7d33a11d995b13a1dd25` | `2396120` | ❎ | +| CurveRouters | | | | +| | `0x445FE580eF8d70FF569aB36e80c647af338db351` | `1362056` | ✅ | +| | `0x0E9fBb167DF83EdE3240D6a5fa5d40c6C6851e15` | `4530115` | ✅ | +| UniswapForks | | | | +| | `0x1b02da8cb0d097eb8d57a175b88c7d8b47997506` | `73` | ✅ | + +### Aurora + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| CurveRouters | | | | +| | `0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858` | `62440525` | ✅ | +| UniswapForks | | | | +| | `0x2CB45Edb4517d5947aFdE3BEAbF95A582506858B` | | ✅ | + +### Avalanche + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| AaveOracle | `0xEBd36016B3eD09D4693Ed4251c67Bd858c3c7C9C` | `11970477` | ❎ | +| CurveRouters | | | | +| | `0x8474DdbE98F5aA3179B3B3F5942D724aFcdec9f6` | `5254206` | ✅ | +| | `0x90f421832199e93d01b64DaF378b183809EB0988` | `9384663` | ✅ | +| UniswapForks | | | | +| | `0x60aE616a2155Ee3d9A68541Ba4544862310933d4` | `2486393` | ✅ | +| | `0xE54Ca86531e17Ef3616d22Ca28b0D458b6C89106` | `56879` | ✅ | +| | `0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506` | `506236` | ✅ | + +### BSC + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| UniswapForks | | | | +| | `0x10ED43C718714eb63d5aA57B78B54704E256024E` | `6810080` | ✅ | +| | `0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F` | `586899` | ✅ | + +### Celo + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| UniswapForks | | | | +| | `0xe3d8bd6aed4f159bc8000a9cd47cffdb95f96121` | `5272598` | ✅ | +| | `0x1b02da8cb0d097eb8d57a175b88c7d8b47997506` | `7254057` | ✅ | + +### Cronos + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| UniswapForks | | | | +| | `0x145863eb42cf62847a6ca784e6416c1682b1b2ae` | `5247` | ✅ | + +### Fantom + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| YearnLens | `0x57aa88a0810dfe3f9b71a9b179dd8bf5f956c46a` | `17091856` | ❎ | +| Curve | `0x0b53e9df372e72d8fdcdbedfbb56059957a37128` | `27067399` | ❎ | +| SushiSwap | `0x44536de2220987d098d1d29d3aafc7f7348e9ee4` | `3809480` | ❎ | +| CurveRouters | | | | +| | `0x0f854EA9F38ceA4B1c2FC79047E9D0134419D5d6` | `5655918` | ✅ | +| | `0x4fb93D7d320E8A263F22f62C2059dFC2A8bCbC4c` | `27552509` | ✅ | +| UniswapForks | | | | +| | `0xbe4fc72f8293f9d3512d58b969c98c3f676cb957` | `3796241` | ✅ | +| | `0x16327E3FbDaCA3bcF7E38F5Af2599D2DDc33aE52` | `4250168` | ✅ | +| | `0x1b02da8cb0d097eb8d57a175b88c7d8b47997506` | `2457904` | ✅ | + +### Fuse + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| UniswapForks | | | | +| | `0xe3f85aad0c8dd7337427b9df5d0fb741d65eeeb5` | `15645719` | ✅ | +| | `0x1b02da8cb0d097eb8d57a175b88c7d8b47997506` | `12936314` | ✅ | + +### Gnosis + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| CurveRouters | | | | +| | `0x55E91365697EB8032F98290601847296eC847210` | `20754886` | ✅ | +| | `0x8A4694401bE8F8FCCbC542a3219aF1591f87CE17` | `23334728` | ✅ | +| UniswapForks | | | | +| | `0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506` | `14735910` | ✅ | + +### Harmony + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| AaveOracle | `0x3c90887ede8d65ccb2777a5d577beab2548280ad` | `23930344` | ❎ | +| CurveRouters | | | | +| | `0x0a53FaDa2d943057C47A301D25a4D9b3B8e01e8E` | `18003250` | ✅ | +| UniswapForks | | | | +| | `0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506` | `11256069` | ✅ | + +### Mainnet + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| YearnLens | `0x83d95e0d5f402511db06817aff3f9ea88224b030` | `12242339` | ❎ | +| ChainLink | `0x47Fb2585D2C56Fe188D0E6ec628a38b74fCeeeDf` | `12864088` | ❎ | +| Curve | `0x25BF7b72815476Dd515044F9650Bf79bAd0Df655` | `12370088` | ❎ | +| SushiSwap | `0x8263e161A855B644f582d9C164C66aABEe53f927` | `12692284` | ❎ | +| CurveRouters | | | | +| | `0x7D86446dDb609eD0F5f8684AcF30380a356b2B4c` | `11154794` | ✅ | +| | `0x8F942C20D02bEfc377D41445793068908E2250D0` | `13986752` | ✅ | +| UniswapForks | | | | +| | `0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F` | `10794261` | ✅ | +| | `0x7a250d5630b4cf539739df2c5dacb4c659f2488d` | `10207858` | ✅ | + +### Moonbeam + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| CurveRouters | | | | +| | `0xC2b1DF84112619D190193E48148000e3990Bf627` | `1452049` | ✅ | +| UniswapForks | | | | +| | `0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506` | `503734` | ✅ | + +### Optimism + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| YearnLens | `0xb082d9f4734c535d9d80536f7e87a6f4f471bf65` | `18109291` | ❎ | +| AaveOracle | `0xD81eb3728a631871a7eBBaD631b5f424909f0c77` | `4365625` | ❎ | +| Curve | `0x0ffe8434eae67c9838b12c3cd11ac4005daa7227` | `18368996` | ❎ | +| SushiSwap | `0x5fd3815dcb668200a662114fbc9af13ac0a55b4d` | `18216910` | ❎ | +| CurveRouters | | | | +| | `0xC5cfaDA84E902aD92DD40194f0883ad49639b023` | `2373837` | ✅ | +| | `0x7DA64233Fefb352f8F501B357c018158ED8aA455` | `3729171` | ✅ | +| UniswapForks | | | | +| | `0x9c12939390052919aF3155f41Bf4160Fd3666A6f` | `19702709` | ✅ | + +### Polygon + +| **Method** | **Address** | **StartBlock** | **MultiCall** | +| ------------ | :------------------------------------------: | :------------: | :-----------: | +| AaveOracle | `0xb023e699F5a33916Ea823A16485e259257cA8Bd1` | `25825996` | ❎ | +| CurveRouters | | | | +| | `0x094d12e5b541784701FD8d65F11fc0598FBC6332` | `13991825` | ✅ | +| | `0x47bB542B9dE58b970bA50c9dae444DDB4c16751a` | `23556360` | ✅ | +| UniswapForks | | | | +| | `0xa5e0829caced8ffdd4de3c43696c57f7d7a678ff` | `4931900` | ✅ | +| | `0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506` | `11333235` | ✅ | diff --git a/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsCurve.ts b/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsCurve.ts new file mode 100644 index 0000000000..2ae17d094c --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsCurve.ts @@ -0,0 +1,47 @@ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType, OracleContract } from "../common/types"; +import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { CalculationsCurve as CalculationsCurveContract } from "../../../generated/Staking/CalculationsCurve"; + +export function getCalculationsCurveContract( + contract: OracleContract, + block: ethereum.Block | null = null +): CalculationsCurveContract | null { + if ( + (block && contract.startBlock.gt(block.number)) || + utils.isNullAddress(contract.address) + ) + return null; + + return CalculationsCurveContract.bind(contract.address); +} + +export function getTokenPriceUSDC( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + + if (!config || config.curveCalculationsBlacklist().includes(tokenAddr)) + return new CustomPriceType(); + + const calculationCurveContract = getCalculationsCurveContract( + config.curveCalculations(), + block + ); + if (!calculationCurveContract) return new CustomPriceType(); + + const tokenPrice: BigDecimal = utils + .readValue( + calculationCurveContract.try_getCurvePriceUsdc(tokenAddr), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + return CustomPriceType.initialize( + tokenPrice, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.CURVE_CALCULATIONS + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsSushiswap.ts b/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsSushiswap.ts new file mode 100644 index 0000000000..4ce142fc78 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/calculations/CalculationsSushiswap.ts @@ -0,0 +1,47 @@ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType, OracleContract } from "../common/types"; +import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { CalculationsSushiSwap as CalculationsSushiContract } from "../../../generated/Staking/CalculationsSushiSwap"; + +export function getSushiSwapContract( + contract: OracleContract, + block: ethereum.Block | null = null +): CalculationsSushiContract | null { + if ( + (block && contract.startBlock.gt(block.number)) || + utils.isNullAddress(contract.address) + ) + return null; + + return CalculationsSushiContract.bind(contract.address); +} + +export function getTokenPriceUSDC( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + + if (!config || config.sushiCalculationsBlacklist().includes(tokenAddr)) + return new CustomPriceType(); + + const calculationSushiContract = getSushiSwapContract( + config.sushiCalculations(), + block + ); + if (!calculationSushiContract) return new CustomPriceType(); + + const tokenPrice: BigDecimal = utils + .readValue( + calculationSushiContract.try_getPriceUsdc(tokenAddr), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + return CustomPriceType.initialize( + tokenPrice, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.SUSHI_CALCULATIONS + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/common/constants.ts b/subgraphs/mantle-staked-eth/src/prices/common/constants.ts new file mode 100644 index 0000000000..a809e7fb45 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/common/constants.ts @@ -0,0 +1,43 @@ +import { Address, BigDecimal, BigInt } from "@graphprotocol/graph-ts"; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// COMMON //////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export namespace NULL { + export const TYPE_STRING = "0x0000000000000000000000000000000000000000"; + export const TYPE_ADDRESS = Address.fromString(TYPE_STRING); +} + +export namespace OracleType { + export const AAVE_ORACLE = "AaveOracle"; + export const CURVE_ROUTER = "CurveRouter"; + export const CHAINLINK_FEED = "ChainlinkFeed"; + export const YEARN_LENS_ORACLE = "YearnLensOracle"; + export const CURVE_CALCULATIONS = "CurveCalculations"; + export const UNISWAP_FORKS_ROUTER = "UniswapForksRouter"; + export const SUSHI_CALCULATIONS = "SushiswapCalculations"; +} + +export const CHAIN_LINK_USD_ADDRESS = Address.fromString( + "0x0000000000000000000000000000000000000348" +); + +export const PRICE_LIB_VERSION = "1.3.2"; + +export const INT_ZERO = 0 as i32; +export const INT_ONE = 1 as i32; +export const INT_TWO = 2 as i32; +export const INT_NEGATIVE_ONE = -1 as i32; + +export const BIGINT_ZERO = BigInt.fromI32(0); +export const BIGINT_ONE = BigInt.fromI32(1); +export const BIGINT_TEN = BigInt.fromI32(10); +export const BIGINT_TEN_THOUSAND = BigInt.fromI32(10000); + +export const BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); +export const BIGDECIMAL_USD_PRICE = BigDecimal.fromString("1000000"); + +export const AAVE_ORACLE_DECIMALS = 8; +export const DEFAULT_USDC_DECIMALS = 6; +export const DEFAULT_DECIMALS = BigInt.fromI32(18); diff --git a/subgraphs/mantle-staked-eth/src/prices/common/types.ts b/subgraphs/mantle-staked-eth/src/prices/common/types.ts new file mode 100644 index 0000000000..bf2dfe1a41 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/common/types.ts @@ -0,0 +1,147 @@ +import * as constants from "./constants"; +import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; + +export class Wrapped { + inner: T; + + constructor(inner: T) { + this.inner = inner; + } +} + +export class OracleContract { + private _contractAddress: string; + private _contractStartBlock: i32; + + constructor( + contractAddress: string = constants.NULL.TYPE_STRING, + startBlock: i32 = -1 + ) { + this._contractAddress = contractAddress; + this._contractStartBlock = startBlock; + } + + get address(): Address { + return Address.fromString(this._contractAddress); + } + + get startBlock(): BigInt { + return BigInt.fromI32(this._contractStartBlock); + } +} + +export class CustomPriceType { + // `null` indicates a reverted call. + private _usdPrice: Wrapped; + private _decimals: Wrapped; + private _oracleType: string; + private _liquidity: Wrapped; + + constructor() { + this._usdPrice = new Wrapped(constants.BIGDECIMAL_ZERO); + this._decimals = new Wrapped(constants.BIGINT_ZERO.toI32() as u8); + this._oracleType = ""; + this._liquidity = new Wrapped(constants.BIGDECIMAL_ZERO); + } + + static initialize( + _usdPrice: BigDecimal, + _decimals: i32 = 0, + _oracleType: string = "", + _liquidity: BigDecimal | null = null + ): CustomPriceType { + const result = new CustomPriceType(); + result._usdPrice = new Wrapped(_usdPrice); + result._decimals = new Wrapped(_decimals as u8); + result._oracleType = _oracleType; + if (_liquidity) result._liquidity = new Wrapped(_liquidity); + + return result; + } + + get reverted(): bool { + return this._usdPrice.inner == constants.BIGDECIMAL_ZERO; + } + + get usdPrice(): BigDecimal { + return changetype>(this._usdPrice).inner.div( + constants.BIGINT_TEN.pow(this.decimals as u8).toBigDecimal() + ); + } + + get decimals(): i32 { + return changetype>(this._decimals).inner; + } + + get oracleType(): string { + return this._oracleType; + } + + get liquidity(): BigDecimal { + return this._liquidity.inner; + } + + setLiquidity(liquidity: BigDecimal): void { + this._liquidity = new Wrapped(liquidity); + } +} + +export interface OracleConfig { + oracleCount(): number; + oracleOrder(): string[]; +} + +export class OracleType { + oracleCount: number; + oracleOrder: string[]; + + constructor() { + this.oracleCount = constants.INT_ONE; + this.oracleOrder = [ + constants.OracleType.YEARN_LENS_ORACLE, + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.SUSHI_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.UNISWAP_FORKS_ROUTER, + ]; + } + + setOracleConfig(override: OracleConfig): void { + this.oracleCount = override.oracleCount(); + this.oracleOrder = override.oracleOrder(); + } +} + +export interface Configurations { + network(): string; + + yearnLens(): OracleContract; + chainLink(): OracleContract; + yearnLensBlacklist(): Address[]; + + aaveOracle(): OracleContract; + aaveOracleBlacklist(): Address[]; + + curveCalculations(): OracleContract; + curveCalculationsBlacklist(): Address[]; + + sushiCalculations(): OracleContract; + sushiCalculationsBlacklist(): Address[]; + + uniswapForks(): OracleContract[]; + curveRegistry(): OracleContract[]; + + hardcodedStables(): Address[]; + + ethAddress(): Address; + wethAddress(): Address; + usdcAddress(): Address; + + usdcTokenDecimals(): BigInt; + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null; +} diff --git a/subgraphs/mantle-staked-eth/src/prices/common/utils.ts b/subgraphs/mantle-staked-eth/src/prices/common/utils.ts new file mode 100644 index 0000000000..552702cefb --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/common/utils.ts @@ -0,0 +1,181 @@ +import * as BSC from "../config/bsc"; +import * as CELO from "../config/celo"; +import * as FUSE from "../config/fuse"; +import * as XDAI from "../config/gnosis"; +import * as CRONOS from "../config/cronos"; +import * as AURORA from "../config/aurora"; +import * as FANTOM from "../config/fantom"; +import * as POLYGON from "../config/polygon"; +import * as MAINNET from "../config/mainnet"; +import * as HARMONY from "../config/harmony"; +import * as MOONBEAM from "../config/moonbeam"; +import * as OPTIMISM from "../config/optimism"; +import * as AVALANCHE from "../config/avalanche"; +import * as ARBITRUM_ONE from "../config/arbitrum"; + +import { Configurations, CustomPriceType } from "./types"; +import * as constants from "./constants"; +import * as TEMPLATE from "../config/template"; +import { _ERC20 } from "../../../generated/Staking/_ERC20"; +import { + Address, + BigInt, + BigDecimal, + dataSource, + ethereum, +} from "@graphprotocol/graph-ts"; + +export function isNullAddress(tokenAddr: Address): boolean { + return tokenAddr.equals(constants.NULL.TYPE_ADDRESS); +} + +export function bigIntToBigDecimal( + quantity: BigInt, + decimals: i32 = constants.DEFAULT_DECIMALS.toI32() +): BigDecimal { + return quantity.divDecimal( + constants.BIGINT_TEN.pow(decimals as u8).toBigDecimal() + ); +} + +export function readValue( + callResult: ethereum.CallResult, + defaultValue: T +): T { + return callResult.reverted ? defaultValue : callResult.value; +} + +export function getTokenName(tokenAddr: Address): string { + const tokenContract = _ERC20.bind(tokenAddr); + const name = readValue(tokenContract.try_name(), ""); + + return name; +} + +export function getTokenDecimals(tokenAddr: Address): BigInt { + const tokenContract = _ERC20.bind(tokenAddr); + + const decimals = readValue( + tokenContract.try_decimals(), + constants.DEFAULT_DECIMALS + ); + + return decimals; +} + +export function getTokenSupply(tokenAddr: Address): BigInt { + const tokenContract = _ERC20.bind(tokenAddr); + + const totalSupply = readValue( + tokenContract.try_totalSupply(), + constants.BIGINT_ONE + ); + + return totalSupply; +} + +export function getConfig(): Configurations { + const network = dataSource.network(); + + if (network == XDAI.NETWORK_STRING) { + return new XDAI.config(); + } else if (network == AURORA.NETWORK_STRING) { + return new AURORA.config(); + } else if (network == BSC.NETWORK_STRING) { + return new BSC.config(); + } else if (network == FANTOM.NETWORK_STRING) { + return new FANTOM.config(); + } else if (network == POLYGON.NETWORK_STRING) { + return new POLYGON.config(); + } else if (network == MAINNET.NETWORK_STRING) { + return new MAINNET.config(); + } else if (network == HARMONY.NETWORK_STRING) { + return new HARMONY.config(); + } else if (network == MOONBEAM.NETWORK_STRING) { + return new MOONBEAM.config(); + } else if (network == OPTIMISM.NETWORK_STRING) { + return new OPTIMISM.config(); + } else if (network == AVALANCHE.NETWORK_STRING) { + return new AVALANCHE.config(); + } else if (network == ARBITRUM_ONE.NETWORK_STRING) { + return new ARBITRUM_ONE.config(); + } else if (network == CRONOS.NETWORK_STRING) { + return new CRONOS.config(); + } else if (network == CELO.NETWORK_STRING) { + return new CELO.config(); + } else if (network == FUSE.NETWORK_STRING) { + return new FUSE.config(); + } + + return new TEMPLATE.config(); +} + +function sortByPrices(prices: CustomPriceType[]): CustomPriceType[] { + const pricesSorted = prices.sort(function (a, b) { + const x = a.usdPrice; + const y = b.usdPrice; + + if (x < y) return -1; + if (x > y) return 1; + return 0; + }); + + return pricesSorted; +} + +function pairwiseDiffOfPrices(prices: CustomPriceType[]): BigDecimal[] { + const diff: BigDecimal[] = []; + for (let i = 1; i < prices.length; i++) { + const x = prices[i].usdPrice; + const y = prices[i - 1].usdPrice; + + diff.push(x.minus(y)); + } + + return diff; +} + +export function kClosestPrices( + k: i32, + prices: CustomPriceType[] +): CustomPriceType[] { + // sort by USD prices + const pricesSorted = sortByPrices(prices); + + // pairwise difference in USD prices + const pairwiseDiff = pairwiseDiffOfPrices(pricesSorted); + + // k minimum difference values and their original indexes + const pairwiseDiffCopy = pairwiseDiff.map((x: BigDecimal) => x); + const pairwiseDiffSortedSlice = pairwiseDiffCopy.sort().slice(0, k); + const minDiffAtIdx: i32[] = []; + for (let i = 0; i < pairwiseDiffSortedSlice.length; i++) { + const idx = pairwiseDiff.indexOf(pairwiseDiffSortedSlice[i]); + minDiffAtIdx.push(idx as i32); + } + + // k closest USD price values + const kClosestPrices: CustomPriceType[] = []; + for (let i = 0; i < minDiffAtIdx.length; i++) { + if (!kClosestPrices.includes(pricesSorted[minDiffAtIdx[i]])) { + kClosestPrices.push(pricesSorted[minDiffAtIdx[i]]); + } + if (!kClosestPrices.includes(pricesSorted[minDiffAtIdx[i] + 1])) { + kClosestPrices.push(pricesSorted[minDiffAtIdx[i] + 1]); + } + } + + return kClosestPrices; +} + +export function averagePrice(prices: CustomPriceType[]): CustomPriceType { + let summationUSDPrice = constants.BIGDECIMAL_ZERO; + for (let i = 0; i < prices.length; i++) { + summationUSDPrice = summationUSDPrice.plus(prices[i].usdPrice); + } + + return CustomPriceType.initialize( + summationUSDPrice.div(new BigDecimal(BigInt.fromI32(prices.length as i32))), + constants.DEFAULT_USDC_DECIMALS + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/arbitrum.ts b/subgraphs/mantle-staked-eth/src/prices/config/arbitrum.ts new file mode 100644 index 0000000000..145692b274 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/arbitrum.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "arbitrum-one"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract( + "0x043518ab266485dc085a1db095b8d9c2fc78e9b9", + 2396321 +); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract( + "0xb56c2f0b653b2e0b10c9b928c8580ac5df02c7c7", + 7740843 +); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract( + "0x5ea7e501c9a23f4a76dc7d33a11d995b13a1dd25", + 2396120 +); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract( + "0x3268c3bda100ef0ff3c2d044f23eab62c80d78d2", + 11707234 +); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x445fe580ef8d70ff569ab36e80c647af338db351", 1362056), + new OracleContract("0x0e9fbb167df83ede3240d6a5fa5d40c6c6851e15", 4530115), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 73), // SushiSwap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +); +export const WETH_ADDRESS = Address.fromString( + "0x82af49447d8a07e3bd95bd0d56f35241523fbab1" +); +export const USDC_ADDRESS = Address.fromString( + "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/aurora.ts b/subgraphs/mantle-staked-eth/src/prices/config/aurora.ts new file mode 100644 index 0000000000..628013c4bf --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/aurora.ts @@ -0,0 +1,133 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "aurora"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x5b5cfe992adac0c9d48e05854b2d91c73a003858", 62440526), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x2cb45edb4517d5947afde3beabf95a582506858b", 49607893), // TriSolaris +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x8bec47865ade3b172a928df8f990bc7f2a3b9f79" // Aurora +); +export const WETH_ADDRESS = Address.fromString( + "0xc9bdeed33cd01541e1eed10f90519d2c06fe3feb" // WETH +); +export const USDC_ADDRESS = Address.fromString( + "0xb12bfca5a55806aaf64e99521918a4bf0fc40802" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/avalanche.ts b/subgraphs/mantle-staked-eth/src/prices/config/avalanche.ts new file mode 100644 index 0000000000..5d181039be --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/avalanche.ts @@ -0,0 +1,140 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "avalanche"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract( + "0xebd36016b3ed09d4693ed4251c67bd858c3c7c9c", + 11970477 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x8474ddbe98f5aa3179b3b3f5942d724afcdec9f6", 5254206), + new OracleContract("0x90f421832199e93d01b64daf378b183809eb0988", 9384663), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x60ae616a2155ee3d9a68541ba4544862310933d4", 2486393), // TraderJOE + new OracleContract("0xe54ca86531e17ef3616d22ca28b0d458b6c89106", 56879), // Pangolin + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 506236), // Sushiswap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab" +); +export const WETH_ADDRESS = Address.fromString( + "0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7" +); +export const USDC_ADDRESS = Address.fromString( + "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/bsc.ts b/subgraphs/mantle-staked-eth/src/prices/config/bsc.ts new file mode 100644 index 0000000000..ecef0f8d3e --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/bsc.ts @@ -0,0 +1,130 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "bsc"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x10ed43c718714eb63d5aa57b78b54704e256024e", 6810080), // PancakeSwap v2 + new OracleContract("0x05ff2b0db69458a0750badebc4f9e13add608c7f", 586899), // PancakeSwap v1 +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(18); + +export const ETH_ADDRESS = constants.NULL.TYPE_ADDRESS; +export const WETH_ADDRESS = Address.fromString( + "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c" +); +export const USDC_ADDRESS = Address.fromString( + "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/celo.ts b/subgraphs/mantle-staked-eth/src/prices/config/celo.ts new file mode 100644 index 0000000000..2318f6b643 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/celo.ts @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "celo"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0xe3d8bd6aed4f159bc8000a9cd47cffdb95f96121", 5272598), // Ubeswap + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 7254057), // Sushiswap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x122013fd7df1c6f636a5bb8f03108e876548b455" +); +export const WETH_ADDRESS = Address.fromString( + "0x471ece3750da237f93b8e339c536989b8978a438" // Celo native asset (CELO) +); +export const USDC_ADDRESS = Address.fromString( + "0x37f750b7cc259a2f741af45294f6a16572cf5cad" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/cronos.ts b/subgraphs/mantle-staked-eth/src/prices/config/cronos.ts new file mode 100644 index 0000000000..f847104822 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/cronos.ts @@ -0,0 +1,131 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "cronos"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x145863eb42cf62847a6ca784e6416c1682b1b2ae", 5247), // VVS Finance +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0xe44fd7fcb2b1581822d0c862b68222998a0c299a" +); +export const WETH_ADDRESS = Address.fromString( + "0x5c7f8a570d578ed84e63fdfa7b1ee72deae1ae23" // Wrapped CRO (WCRO) +); +export const USDC_ADDRESS = Address.fromString( + "0xc21223249ca28397b4b6541dffaecc539bff0c59" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/fantom.ts b/subgraphs/mantle-staked-eth/src/prices/config/fantom.ts new file mode 100644 index 0000000000..9d461b497a --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/fantom.ts @@ -0,0 +1,145 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "fantom"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract( + "0x57aa88a0810dfe3f9b71a9b179dd8bf5f956c46a", + 17091856 +); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract( + "0x44536de2220987d098d1d29d3aafc7f7348e9ee4", + 3809480 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract( + "0x0b53e9df372e72d8fdcdbedfbb56059957a37128", + 27067399 +); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x0f854ea9f38cea4b1c2fc79047e9d0134419d5d6", 5655918), + new OracleContract("0x4fb93d7d320e8a263f22f62c2059dfc2a8bcbc4c", 27552509), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0xbe4fc72f8293f9d3512d58b969c98c3f676cb957", 3796241), // Uniswap v2 + new OracleContract("0x16327e3fbdaca3bcf7e38f5af2599d2ddc33ae52", 4250168), // Spiritswap + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 2457904), // Sushiswap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x658b0c7613e890ee50b8c4bc6a3f41ef411208ad" +); +export const WETH_ADDRESS = Address.fromString( + "0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83" +); +export const USDC_ADDRESS = Address.fromString( + "0x04068da6c83afcfa0e13ba15a6696662335d5b75" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/fuse.ts b/subgraphs/mantle-staked-eth/src/prices/config/fuse.ts new file mode 100644 index 0000000000..2392dba83d --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/fuse.ts @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "fuse"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0xe3f85aad0c8dd7337427b9df5d0fb741d65eeeb5", 15645719), // Voltage Finance + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 12936314), // Sushiswap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0xa722c13135930332eb3d749b2f0906559d2c5b99" +); +export const WETH_ADDRESS = Address.fromString( + "0x0be9e53fd7edac9f859882afdda116645287c629" // Wrapped Fuse (WFUSE) +); +export const USDC_ADDRESS = Address.fromString( + "0x620fd5fa44be6af63715ef4e65ddfa0387ad13f5" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/gnosis.ts b/subgraphs/mantle-staked-eth/src/prices/config/gnosis.ts new file mode 100644 index 0000000000..8c4abb1db8 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/gnosis.ts @@ -0,0 +1,134 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING: string = "xdai"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x55e91365697eb8032f98290601847296ec847210", 20754886), + new OracleContract("0x8a4694401be8f8fccbc542a3219af1591f87ce17", 23334728), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 14735910), // SushiSwap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1" +); +export const WETH_ADDRESS = Address.fromString( + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" +); +export const USDC_ADDRESS = Address.fromString( + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/harmony.ts b/subgraphs/mantle-staked-eth/src/prices/config/harmony.ts new file mode 100644 index 0000000000..4ed750f8ca --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/harmony.ts @@ -0,0 +1,136 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "harmony"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract( + "0xb56c2f0b653b2e0b10c9b928c8580ac5df02c7c7", + 23930344 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x0a53fada2d943057c47a301d25a4d9b3b8e01e8e", 18003250), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 11256069), // SushiSwap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x6983d1e6def3690c4d616b13597a09e6193ea013" +); +export const WETH_ADDRESS = Address.fromString( + "0xcf664087a5bb0237a0bad6742852ec6c8d69a27a" +); +export const USDC_ADDRESS = Address.fromString( + "0x985458e523db3d53125813ed68c274899e9dfab4" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/mainnet.ts b/subgraphs/mantle-staked-eth/src/prices/config/mainnet.ts new file mode 100644 index 0000000000..04177743b7 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/mainnet.ts @@ -0,0 +1,293 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "mainnet"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract( + "0x83d95e0d5f402511db06817aff3f9ea88224b030", + 12242339 +); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract( + "0x47fb2585d2c56fe188d0e6ec628a38b74fceeedf", + 12864088 +); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract( + "0x8263e161a855b644f582d9c164c66aabee53f927", + 12692284 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract( + "0x25bf7b72815476dd515044f9650bf79bad0df655", + 12370088 +); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x7d86446ddb609ed0f5f8684acf30380a356b2b4c", 11154794), + new OracleContract("0x8f942c20d02befc377d41445793068908e2250d0", 13986752), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f", 10794261), // SushiSwap + new OracleContract("0x7a250d5630b4cf539739df2c5dacb4c659f2488d", 10207858), // Uniswap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = [ + Address.fromString("0x5f98805a4e8be255a32880fdec7f6728c6568ba0"), // LUSD + Address.fromString("0x8daebade922df735c38c80c7ebd708af50815faa"), // tBTC + Address.fromString("0x0316eb71485b0ab14103307bf65a021042c6d380"), // Huobi BTC + Address.fromString("0xca3d75ac011bf5ad07a98d02f18225f9bd9a6bdf"), // crvTriCrypto +]; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = [ + Address.fromString("0xca3d75ac011bf5ad07a98d02f18225f9bd9a6bdf"), // crvTriCrypto + Address.fromString("0xc4ad29ba4b3c580e6d59105fff484999997675ff"), // crv3Crypto +]; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = [ + Address.fromString("0xd632f22692fac7611d2aa1c0d552930d43caed3b"), // Curve.fi Factory USD Metapool: Frax + Address.fromString("0x99d8a9c45b2eca8864373a26d1459e3dff1e17f3"), // Magic Internet Money + Address.fromString("0x5a6a4d54456819380173272a5e8e9b9904bdf41b"), // Curve.fi Factory USD Metapool: Magic Internet Money 3Pool + Address.fromString("0xbc6da0fe9ad5f3b0d58160288917aa56653660e9"), // Alchemix USD + Address.fromString("0x43b4fdfd4ff969587185cdb6f0bd875c5fc83f8c"), // Curve.fi Factory USD Metapool: Alchemix USD + Address.fromString("0x57ab1ec28d129707052df4df418d58a2d46d5f51"), // Synth SUSD + Address.fromString("0xc25a3a3b969415c80451098fa907ec722572917f"), // Curve.fi DAI/USDC/USDT/sUSD + Address.fromString("0x0000000000085d4780b73119b644ae5ecd22b376"), // TrueUSD + Address.fromString("0xecd5e75afb02efa118af914515d6521aabd189f1"), // Curve.fi Factory USD Metapool: TrueUSD + Address.fromString("0xfd2a8fa60abd58efe3eee34dd494cd491dc14900"), // Curve.fi aDAI/aUSDC/aUSDT + Address.fromString("0x8ee017541375f6bcd802ba119bddc94dad6911a1"), // Curve.fi Factory USD Metapool: PUSd + Address.fromString("0x5b3b5df2bf2b6543f78e053bd91c4bdd820929f1"), // Curve.fi Factory USD Metapool: USDM + Address.fromString("0x04b727c7e246ca70d496ecf52e6b6280f3c8077d"), // Curve.fi Factory USD Metapool: apeUSDFRAXBP + Address.fromString("0x3175df0976dfa876431c2e9ee6bc45b65d3473cc"), // Curve.fi FRAX/USDC + Address.fromString("0xbcb91e689114b9cc865ad7871845c95241df4105"), // Curve.fi Factory USD Metapool: PWRD Metapool + Address.fromString("0x26ea744e5b887e5205727f55dfbe8685e3b21951"), // iearn USDC + Address.fromString("0xc2cb1040220768554cf699b0d863a3cd4324ce32"), // iearn DAI + Address.fromString("0x04bc0ab673d88ae9dbc9da2380cb6b79c4bca9ae"), // iearn BUSD + Address.fromString("0xe6354ed5bc4b393a5aad09f21c46e101e692d447"), // iearn USDT + Address.fromString("0x3b3ac5386837dc563660fb6a0937dfaa5924333b"), // Curve.fi yDAI/yUSDC/yUSDT/yBUSD + Address.fromString("0xc2f5fea5197a3d92736500fd7733fcc7a3bbdf3f"), // Curve.fi Factory USD Metapool: fUSD-3pool + Address.fromString("0x0c10bf8fcb7bf5412187a595ab97a3609160b5c6"), // Decentralized USD + Address.fromString("0x028171bca77440897b824ca71d1c56cac55b68a3"), // Aave interest bearing DAI + Address.fromString("0x3ed3b47dd13ec9a98b44e6204a523e766b225811"), // Aave interest bearing USDT + Address.fromString("0xbcca60bb61934080951369a648fb03df4f96263c"), // Aave interest bearing USDC + Address.fromString("0x6c5024cd4f8a59110119c56f8933403a539555eb"), // Aave interest bearing SUSD + Address.fromString("0xd71ecff9342a5ced620049e616c5035f1db98620"), // Synth sEUR +]; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////// ORACLE CONFIG OVERRIDES ///////////////////////// +/////////////////////////////////////////////////////////////////////////// + +// https://github.com/messari/subgraphs/issues/2090 +class SpellOverride implements OracleConfig { + oracleCount(): number { + return constants.INT_ONE; + } + oracleOrder(): string[] { + return [ + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.SUSHI_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.UNISWAP_FORKS_ROUTER, + constants.OracleType.YEARN_LENS_ORACLE, + ]; + } +} + +// https://github.com/messari/subgraphs/issues/726 +class StETHOverride implements OracleConfig { + oracleCount(): number { + return constants.INT_ONE; + } + oracleOrder(): string[] { + return [ + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.SUSHI_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.UNISWAP_FORKS_ROUTER, + constants.OracleType.YEARN_LENS_ORACLE, + ]; + } +} + +// https://github.com/messari/subgraphs/issues/2097 +class BaxaOverride implements OracleConfig { + oracleCount(): number { + return constants.INT_ONE; + } + oracleOrder(): string[] { + return [ + constants.OracleType.UNISWAP_FORKS_ROUTER, + constants.OracleType.YEARN_LENS_ORACLE, + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.SUSHI_CALCULATIONS, + ]; + } +} + +// https://github.com/messari/subgraphs/issues/2329 +class DelperOverride implements OracleConfig { + oracleCount(): number { + return constants.INT_ONE; + } + oracleOrder(): string[] { + return [ + constants.OracleType.UNISWAP_FORKS_ROUTER, + constants.OracleType.YEARN_LENS_ORACLE, + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.SUSHI_CALCULATIONS, + ]; + } +} + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +); +export const WETH_ADDRESS = Address.fromString( + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" +); +export const USDC_ADDRESS = Address.fromString( + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + if (tokenAddr || block) { + if ( + tokenAddr && + [ + Address.fromString("0x090185f2135308bad17527004364ebcc2d37e5f6"), // SPELL + ].includes(tokenAddr) + ) { + return new SpellOverride(); + } + if ( + tokenAddr && + [ + Address.fromString("0xae7ab96520de3a18e5e111b5eaab095312d7fe84"), // stETH + ].includes(tokenAddr) && + block && + block.number.gt(BigInt.fromString("14019699")) && + block.number.lt(BigInt.fromString("14941265")) + ) { + return new StETHOverride(); + } + if ( + tokenAddr && + [ + Address.fromString("0x91b08f4a7c1251dfccf5440f8894f8daa10c8de5"), // BAXA + ].includes(tokenAddr) + ) { + return new BaxaOverride(); + } + if ( + tokenAddr && + [ + Address.fromString("0x077416cc6242b3a7d8e42652b8a6a2599fda4a92"), // DPR + ].includes(tokenAddr) + ) { + return new DelperOverride(); + } + } + + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/moonbeam.ts b/subgraphs/mantle-staked-eth/src/prices/config/moonbeam.ts new file mode 100644 index 0000000000..91b3002639 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/moonbeam.ts @@ -0,0 +1,133 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "moonbeam"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0xc2b1df84112619d190193e48148000e3990bf627", 1452049), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x445fe580ef8d70ff569ab36e80c647af338db351", 503734), // SushiSwap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0xfa9343c3897324496a05fc75abed6bac29f8a40f" +); +export const WETH_ADDRESS = Address.fromString( + "0xacc15dc74880c9944775448304b263d191c6077f" +); +export const USDC_ADDRESS = Address.fromString( + "0x818ec0a7fe18ff94269904fced6ae3dae6d6dc0b" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/optimism.ts b/subgraphs/mantle-staked-eth/src/prices/config/optimism.ts new file mode 100644 index 0000000000..14a271641f --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/optimism.ts @@ -0,0 +1,145 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "optimism"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract( + "0xb082d9f4734c535d9d80536f7e87a6f4f471bf65", + 18109291 +); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract( + "0xd81eb3728a631871a7ebbad631b5f424909f0c77", + 4365625 +); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract( + "0x5fd3815dcb668200a662114fbc9af13ac0a55b4d", + 18216910 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract( + "0x0ffe8434eae67c9838b12c3cd11ac4005daa7227", + 18368996 +); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0xc5cfada84e902ad92dd40194f0883ad49639b023", 2373837), + new OracleContract("0x445fe580ef8d70ff569ab36e80c647af338db351", 3729171), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0x9c12939390052919af3155f41bf4160fd3666a6f", 19702709), // Velodrame +]; +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x4200000000000000000000000000000000000042" +); +export const WETH_ADDRESS = Address.fromString( + "0x4200000000000000000000000000000000000006" +); +export const USDC_ADDRESS = Address.fromString( + "0x7f5c764cbc14f9669b88837ca1490cca17c31607" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/polygon.ts b/subgraphs/mantle-staked-eth/src/prices/config/polygon.ts new file mode 100644 index 0000000000..55d2778512 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/polygon.ts @@ -0,0 +1,139 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { BigInt, Address, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "matic"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract( + "0xb023e699f5a33916ea823a16485e259257ca8bd1", + 25825996 +); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); + +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = [ + new OracleContract("0x094d12e5b541784701fd8d65f11fc0598fbc6332", 13991825), + new OracleContract("0x47bb542b9de58b970ba50c9dae444ddb4c16751a", 23556360), +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = [ + new OracleContract("0xa5e0829caced8ffdd4de3c43696c57f7d7a678ff", 4931900), // QuickSwap + new OracleContract("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506", 11333235), // SushiSwap +]; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = Address.fromString( + "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619" +); +export const WETH_ADDRESS = Address.fromString( + "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270" +); +export const USDC_ADDRESS = Address.fromString( + "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" +); + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return null; + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/config/template.ts b/subgraphs/mantle-staked-eth/src/prices/config/template.ts new file mode 100644 index 0000000000..c56b0e9785 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/config/template.ts @@ -0,0 +1,142 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/no-unused-vars */ +import * as constants from "../common/constants"; +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { Configurations, OracleConfig, OracleContract } from "../common/types"; + +export const NETWORK_STRING = "default"; + +/////////////////////////////////////////////////////////////////////////// +///////////////////// CALCULATIONS/ORACLE CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_CONTRACT_ADDRESS = new OracleContract(); +export const CHAIN_LINK_CONTRACT_ADDRESS = new OracleContract(); +export const AAVE_ORACLE_CONTRACT_ADDRESS = new OracleContract(); +export const SUSHISWAP_CALCULATIONS_ADDRESS = new OracleContract(); + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////// CURVE CONTRACT ////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const CURVE_CALCULATIONS_ADDRESS = new OracleContract(); +export const CURVE_REGISTRY_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// UNISWAP FORKS CONTRACT //////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const UNISWAP_FORKS_ROUTER_ADDRESSES: OracleContract[] = []; + +/////////////////////////////////////////////////////////////////////////// +/////////////////////////// BLACKLISTED TOKENS //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const YEARN_LENS_BLACKLIST: Address[] = []; +export const AAVE_ORACLE_BLACKLIST: Address[] = []; +export const CURVE_CALCULATIONS_BLACKSLIST: Address[] = []; +export const SUSHI_CALCULATIONS_BLACKSLIST: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +//////////////////////////// HARDCODED STABLES //////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const HARDCODED_STABLES: Address[] = []; + +/////////////////////////////////////////////////////////////////////////// +///////////////////////// ORACLE CONFIG OVERRIDES ///////////////////////// +/////////////////////////////////////////////////////////////////////////// + +class SomeOverride implements OracleConfig { + oracleCount(): number { + return constants.INT_ONE; + } + oracleOrder(): string[] { + return [ + constants.OracleType.YEARN_LENS_ORACLE, + constants.OracleType.CHAINLINK_FEED, + constants.OracleType.CURVE_CALCULATIONS, + constants.OracleType.SUSHI_CALCULATIONS, + constants.OracleType.CURVE_ROUTER, + constants.OracleType.UNISWAP_FORKS_ROUTER, + ]; + } +} + +/////////////////////////////////////////////////////////////////////////// +///////////////////////////////// HELPERS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////////// + +export const USDC_TOKEN_DECIMALS = BigInt.fromI32(6); + +export const ETH_ADDRESS = constants.NULL.TYPE_ADDRESS; +export const WETH_ADDRESS = constants.NULL.TYPE_ADDRESS; +export const USDC_ADDRESS = constants.NULL.TYPE_ADDRESS; + +export class config implements Configurations { + network(): string { + return NETWORK_STRING; + } + + yearnLens(): OracleContract { + return YEARN_LENS_CONTRACT_ADDRESS; + } + chainLink(): OracleContract { + return CHAIN_LINK_CONTRACT_ADDRESS; + } + yearnLensBlacklist(): Address[] { + return YEARN_LENS_BLACKLIST; + } + + aaveOracle(): OracleContract { + return AAVE_ORACLE_CONTRACT_ADDRESS; + } + aaveOracleBlacklist(): Address[] { + return AAVE_ORACLE_BLACKLIST; + } + + curveCalculations(): OracleContract { + return CURVE_CALCULATIONS_ADDRESS; + } + curveCalculationsBlacklist(): Address[] { + return CURVE_CALCULATIONS_BLACKSLIST; + } + + sushiCalculations(): OracleContract { + return SUSHISWAP_CALCULATIONS_ADDRESS; + } + sushiCalculationsBlacklist(): Address[] { + return SUSHI_CALCULATIONS_BLACKSLIST; + } + + uniswapForks(): OracleContract[] { + return UNISWAP_FORKS_ROUTER_ADDRESSES; + } + curveRegistry(): OracleContract[] { + return CURVE_REGISTRY_ADDRESSES; + } + + hardcodedStables(): Address[] { + return HARDCODED_STABLES; + } + + ethAddress(): Address { + return ETH_ADDRESS; + } + wethAddress(): Address { + return WETH_ADDRESS; + } + usdcAddress(): Address { + return USDC_ADDRESS; + } + + usdcTokenDecimals(): BigInt { + return USDC_TOKEN_DECIMALS; + } + + getOracleOverride( + tokenAddr: Address | null, + block: ethereum.Block | null + ): OracleConfig | null { + return new SomeOverride(); + } +} diff --git a/subgraphs/mantle-staked-eth/src/prices/index.ts b/subgraphs/mantle-staked-eth/src/prices/index.ts new file mode 100644 index 0000000000..2de22a307e --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/index.ts @@ -0,0 +1,151 @@ +import { + log, + Address, + ethereum, + BigDecimal, + dataSource, +} from "@graphprotocol/graph-ts"; +import { CustomPriceType, OracleType } from "./common/types"; + +import * as utils from "./common/utils"; +import * as constants from "./common/constants"; +import * as AaveOracle from "./oracles/AaveOracle"; +import * as CurveRouter from "./routers/CurveRouter"; +import * as ChainLinkFeed from "./oracles/ChainLinkFeed"; +import * as YearnLensOracle from "./oracles/YearnLensOracle"; +import * as UniswapForksRouter from "./routers/UniswapForksRouter"; +import * as CurveCalculations from "./calculations/CalculationsCurve"; +import * as SushiCalculations from "./calculations/CalculationsSushiswap"; + +export function getUsdPricePerToken( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + if (tokenAddr.equals(constants.NULL.TYPE_ADDRESS)) { + return new CustomPriceType(); + } + + const config = utils.getConfig(); + if (config.network() == "default") { + log.warning("Failed to fetch price: network {} not implemented", [ + dataSource.network(), + ]); + + return new CustomPriceType(); + } + + if (config.hardcodedStables().includes(tokenAddr)) { + return CustomPriceType.initialize( + constants.BIGDECIMAL_USD_PRICE, + constants.DEFAULT_USDC_DECIMALS + ); + } + + const oracle = new OracleType(); + const override = config.getOracleOverride(tokenAddr, block); + if (override) { + oracle.setOracleConfig(override); + } + const oracleCount = oracle.oracleCount; + const oracleOrder = oracle.oracleOrder; + + const prices: CustomPriceType[] = []; + for (let i = 0; i < oracleOrder.length; i++) { + if (prices.length >= oracleCount) { + break; + } + + let oraclePrice = new CustomPriceType(); + + if (oracleOrder[i] == constants.OracleType.YEARN_LENS_ORACLE) { + oraclePrice = YearnLensOracle.getTokenPriceUSDC(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.CHAINLINK_FEED) { + oraclePrice = ChainLinkFeed.getTokenPriceUSDC(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.CURVE_CALCULATIONS) { + oraclePrice = CurveCalculations.getTokenPriceUSDC(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.SUSHI_CALCULATIONS) { + oraclePrice = SushiCalculations.getTokenPriceUSDC(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.AAVE_ORACLE) { + oraclePrice = AaveOracle.getTokenPriceUSDC(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.CURVE_ROUTER) { + oraclePrice = CurveRouter.getCurvePriceUsdc(tokenAddr, block); + } else if (oracleOrder[i] == constants.OracleType.UNISWAP_FORKS_ROUTER) { + oraclePrice = UniswapForksRouter.getTokenPriceUSDC(tokenAddr, block); + } + + if (!oraclePrice.reverted) { + prices.push(oraclePrice); + } + } + + if (prices.length == constants.INT_ZERO) { + log.warning("[Oracle] Failed to Fetch Price, tokenAddr: {}", [ + tokenAddr.toHexString(), + ]); + + return new CustomPriceType(); + } else if (prices.length == constants.INT_ONE) { + return prices[constants.INT_ZERO]; + } else if (prices.length == constants.INT_TWO) { + return utils.averagePrice(prices); + } + + const k = Math.ceil(prices.length / constants.INT_TWO) as i32; + const closestPrices = utils.kClosestPrices(k, prices); + + return utils.averagePrice(closestPrices); +} + +export function getLiquidityBoundPrice( + tokenAddress: Address, + tokenPrice: CustomPriceType, + amount: BigDecimal +): BigDecimal { + const reportedPriceUSD = tokenPrice.usdPrice.times(amount); + const liquidity = tokenPrice.liquidity; + + let liquidityBoundPriceUSD = reportedPriceUSD; + if (liquidity > constants.BIGDECIMAL_ZERO && reportedPriceUSD > liquidity) { + liquidityBoundPriceUSD = liquidity + .div( + constants.BIGINT_TEN.pow( + constants.DEFAULT_USDC_DECIMALS as u8 + ).toBigDecimal() + ) + .times(constants.BIGINT_TEN.pow(tokenPrice.decimals as u8).toBigDecimal()) + .div(amount); + + log.warning( + "[getLiquidityBoundPrice] token: {} (reported price * amount): ({} * {}) bound to available liquidity: {}; new price: {}", + [ + tokenAddress.toHexString(), + tokenPrice.usdPrice.toString(), + amount.toString(), + liquidity.toString(), + liquidityBoundPriceUSD.toString(), + ] + ); + } + + return liquidityBoundPriceUSD; +} + +export function getUsdPrice( + tokenAddr: Address, + amount: BigDecimal, + block: ethereum.Block | null = null +): BigDecimal { + const tokenPrice = getUsdPricePerToken(tokenAddr, block); + + if (!tokenPrice.reverted) { + if ( + tokenPrice.oracleType == constants.OracleType.UNISWAP_FORKS_ROUTER || + tokenPrice.oracleType == constants.OracleType.CURVE_ROUTER + ) { + return getLiquidityBoundPrice(tokenAddr, tokenPrice, amount); + } + return tokenPrice.usdPrice.times(amount); + } + + return constants.BIGDECIMAL_ZERO; +} diff --git a/subgraphs/mantle-staked-eth/src/prices/oracles/AaveOracle.ts b/subgraphs/mantle-staked-eth/src/prices/oracles/AaveOracle.ts new file mode 100644 index 0000000000..4914276fa7 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/oracles/AaveOracle.ts @@ -0,0 +1,44 @@ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType, OracleContract } from "../common/types"; +import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { AaveOracleContract } from "../../../generated/Staking/AaveOracleContract"; + +export function getAaveOracleContract( + contract: OracleContract, + block: ethereum.Block | null = null +): AaveOracleContract | null { + if ( + (block && contract.startBlock.gt(block.number)) || + utils.isNullAddress(contract.address) + ) + return null; + + return AaveOracleContract.bind(contract.address); +} + +export function getTokenPriceUSDC( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + + if (!config || config.aaveOracleBlacklist().includes(tokenAddr)) + return new CustomPriceType(); + + const aaveOracleContract = getAaveOracleContract(config.aaveOracle(), block); + if (!aaveOracleContract) return new CustomPriceType(); + + const tokenPrice: BigDecimal = utils + .readValue( + aaveOracleContract.try_getAssetPrice(tokenAddr), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + return CustomPriceType.initialize( + tokenPrice, + constants.AAVE_ORACLE_DECIMALS, + constants.OracleType.AAVE_ORACLE + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/oracles/ChainLinkFeed.ts b/subgraphs/mantle-staked-eth/src/prices/oracles/ChainLinkFeed.ts new file mode 100644 index 0000000000..51858648cf --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/oracles/ChainLinkFeed.ts @@ -0,0 +1,53 @@ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { Address, ethereum } from "@graphprotocol/graph-ts"; +import { CustomPriceType, OracleContract } from "../common/types"; +import { ChainLinkContract } from "../../../generated/Staking/ChainLinkContract"; + +export function getChainLinkContract( + contract: OracleContract, + block: ethereum.Block | null = null +): ChainLinkContract | null { + if ( + (block && contract.startBlock.gt(block.number)) || + utils.isNullAddress(contract.address) + ) + return null; + + return ChainLinkContract.bind(contract.address); +} + +export function getTokenPriceUSDC( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + if (!config) return new CustomPriceType(); + + const chainLinkContract = getChainLinkContract(config.chainLink(), block); + if (!chainLinkContract) return new CustomPriceType(); + + const result = chainLinkContract.try_latestRoundData( + tokenAddr, + constants.CHAIN_LINK_USD_ADDRESS + ); + + if (!result.reverted) { + const decimals = chainLinkContract.try_decimals( + tokenAddr, + constants.CHAIN_LINK_USD_ADDRESS + ); + + if (decimals.reverted) { + return new CustomPriceType(); + } + + return CustomPriceType.initialize( + result.value.value1.toBigDecimal(), + decimals.value, + constants.OracleType.CHAINLINK_FEED + ); + } + + return new CustomPriceType(); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/oracles/YearnLensOracle.ts b/subgraphs/mantle-staked-eth/src/prices/oracles/YearnLensOracle.ts new file mode 100644 index 0000000000..6273426566 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/oracles/YearnLensOracle.ts @@ -0,0 +1,44 @@ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType, OracleContract } from "../common/types"; +import { Address, BigDecimal, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { YearnLensContract } from "../../../generated/Staking/YearnLensContract"; + +export function getYearnLensContract( + contract: OracleContract, + block: ethereum.Block | null = null +): YearnLensContract | null { + if ( + (block && contract.startBlock.gt(block.number)) || + utils.isNullAddress(contract.address) + ) + return null; + + return YearnLensContract.bind(contract.address); +} + +export function getTokenPriceUSDC( + tokenAddr: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + + if (!config || config.yearnLensBlacklist().includes(tokenAddr)) + return new CustomPriceType(); + + const yearnLensContract = getYearnLensContract(config.yearnLens(), block); + if (!yearnLensContract) return new CustomPriceType(); + + const tokenPrice: BigDecimal = utils + .readValue( + yearnLensContract.try_getPriceUsdcRecommended(tokenAddr), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + return CustomPriceType.initialize( + tokenPrice, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.YEARN_LENS_ORACLE + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/routers/CurveRouter.ts b/subgraphs/mantle-staked-eth/src/prices/routers/CurveRouter.ts new file mode 100644 index 0000000000..4662a8b319 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/routers/CurveRouter.ts @@ -0,0 +1,368 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { getUsdPricePerToken } from ".."; +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType } from "../common/types"; +import { BigInt, Address, BigDecimal, ethereum } from "@graphprotocol/graph-ts"; +import { CurvePool as CurvePoolContract } from "../../../generated/Staking/CurvePool"; +import { CurveRegistry as CurveRegistryContract } from "../../../generated/Staking/CurveRegistry"; + +export function isCurveLpToken( + lpAddress: Address, + block: ethereum.Block +): bool { + const poolAddress = getPoolFromLpToken(lpAddress, block); + if (poolAddress.notEqual(constants.NULL.TYPE_ADDRESS)) return true; + + return false; +} + +export function getPoolFromLpToken( + lpAddress: Address, + block: ethereum.Block | null = null +): Address { + const config = utils.getConfig(); + const curveRegistryAdresses = config.curveRegistry(); + + for (let idx = 0; idx < curveRegistryAdresses.length; idx++) { + const curveRegistry = curveRegistryAdresses[idx]; + if (block && curveRegistry.startBlock.gt(block.number)) continue; + + const curveRegistryContract = CurveRegistryContract.bind( + curveRegistry.address + ); + + const poolAddress = utils.readValue
( + curveRegistryContract.try_get_pool_from_lp_token(lpAddress), + constants.NULL.TYPE_ADDRESS + ); + + if (poolAddress.notEqual(constants.NULL.TYPE_ADDRESS)) return poolAddress; + } + + return constants.NULL.TYPE_ADDRESS; +} + +export function isLpCryptoPool( + lpAddress: Address, + block: ethereum.Block | null = null +): bool { + const poolAddress = getPoolFromLpToken(lpAddress, block); + + if (poolAddress != constants.NULL.TYPE_ADDRESS) { + return isPoolCryptoPool(poolAddress); + } + + return false; +} + +export function isPoolCryptoPool(poolAddress: Address): bool { + const poolContract = CurvePoolContract.bind(poolAddress); + + const priceOracleCall = poolContract.try_price_oracle(); + if (!priceOracleCall.reverted) return true; + + const priceOracle1Call = poolContract.try_price_oracle1( + constants.BIGINT_ZERO + ); + if (!priceOracle1Call.reverted) return true; + + return false; +} + +export function getCurvePriceUsdc( + lpAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + if (isLpCryptoPool(lpAddress, block)) + return cryptoPoolLpPriceUsdc(lpAddress, block); + + const basePrice = getBasePrice(lpAddress, block); + const virtualPrice = getVirtualPrice(lpAddress, block).toBigDecimal(); + + const config = utils.getConfig(); + const usdcTokenDecimals = config.usdcTokenDecimals(); + + const decimalsAdjustment = + constants.DEFAULT_DECIMALS.minus(usdcTokenDecimals); + const priceUsdc = virtualPrice + .times(basePrice.usdPrice) + .times( + constants.BIGINT_TEN.pow(decimalsAdjustment.toI32() as u8).toBigDecimal() + ); + + const liquidity = getLpTokenLiquidityUsdc(lpAddress, block); + + return CustomPriceType.initialize( + priceUsdc, + decimalsAdjustment.plus(constants.DEFAULT_DECIMALS).toI32() as u8, + constants.OracleType.CURVE_ROUTER, + liquidity.usdPrice + ); +} + +export function getBasePrice( + lpAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const poolAddress = getPoolFromLpToken(lpAddress, block); + + if (poolAddress.equals(constants.NULL.TYPE_ADDRESS)) + return new CustomPriceType(); + + const underlyingCoinAddress = getUnderlyingCoinFromPool(poolAddress, block); + const basePrice = getPriceUsdcRecommended(underlyingCoinAddress, block); + + return basePrice; +} + +export function getUnderlyingCoinFromPool( + poolAddress: Address, + block: ethereum.Block | null = null +): Address { + const config = utils.getConfig(); + const curveRegistryAdresses = config.curveRegistry(); + + for (let idx = 0; idx < curveRegistryAdresses.length; idx++) { + const curveRegistry = curveRegistryAdresses[idx]; + if (block && curveRegistry.startBlock.gt(block.number)) continue; + + const curveRegistryContract = CurveRegistryContract.bind( + curveRegistry.address + ); + + const coins = utils.readValue( + curveRegistryContract.try_get_underlying_coins(poolAddress), + [] + ); + + if (coins.length != 0) return getPreferredCoinFromCoins(coins); + } + + return constants.NULL.TYPE_ADDRESS; +} + +export function getPreferredCoinFromCoins(coins: Address[]): Address { + let preferredCoinAddress = constants.NULL.TYPE_ADDRESS; + for (let coinIdx = 0; coinIdx < 8; coinIdx++) { + const coinAddress = coins[coinIdx]; + + if (coinAddress.notEqual(constants.NULL.TYPE_ADDRESS)) { + preferredCoinAddress = coinAddress; + } + // Found preferred coin and we're at the end of the token array + if ( + (preferredCoinAddress.notEqual(constants.NULL.TYPE_ADDRESS) && + coinAddress.equals(constants.NULL.TYPE_ADDRESS)) || + coinIdx == 7 + ) { + break; + } + } + + return preferredCoinAddress; +} + +export function getVirtualPrice( + curveLpTokenAddress: Address, + block: ethereum.Block | null = null +): BigInt { + const config = utils.getConfig(); + const curveRegistryAdresses = config.curveRegistry(); + + for (let idx = 0; idx < curveRegistryAdresses.length; idx++) { + const curveRegistry = curveRegistryAdresses[idx]; + if (block && curveRegistry.startBlock.gt(block.number)) continue; + + const curveRegistryContract = CurveRegistryContract.bind( + curveRegistry.address + ); + + const virtualPriceCall = + curveRegistryContract.try_get_virtual_price_from_lp_token( + curveLpTokenAddress + ); + + if (!virtualPriceCall.reverted) return virtualPriceCall.value; + } + + return constants.BIGINT_ZERO; +} + +export function getPriceUsdcRecommended( + tokenAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + return getUsdPricePerToken(tokenAddress, block); +} + +export function cryptoPoolLpPriceUsdc( + lpAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const totalSupply = utils.getTokenSupply(lpAddress); + + const totalValueUsdc = cryptoPoolLpTotalValueUsdc(lpAddress, block); + const priceUsdc = totalValueUsdc + .times( + constants.BIGINT_TEN.pow( + constants.DEFAULT_DECIMALS.toI32() as u8 + ).toBigDecimal() + ) + .div(totalSupply.toBigDecimal()); + + return CustomPriceType.initialize( + priceUsdc, + 0, + constants.OracleType.CURVE_ROUTER + ); +} + +export function cryptoPoolLpTotalValueUsdc( + lpAddress: Address, + block: ethereum.Block | null = null +): BigDecimal { + const poolAddress = getPoolFromLpToken(lpAddress, block); + + const underlyingTokensAddresses = + cryptoPoolUnderlyingTokensAddressesByPoolAddress(poolAddress); + + let totalValue = constants.BIGDECIMAL_ZERO; + + for ( + let tokenIdx = 0; + tokenIdx < underlyingTokensAddresses.length; + tokenIdx++ + ) { + const tokenValueUsdc = cryptoPoolTokenAmountUsdc( + poolAddress, + underlyingTokensAddresses[tokenIdx], + BigInt.fromI32(tokenIdx), + block + ); + totalValue = totalValue.plus(tokenValueUsdc); + } + + return totalValue; +} + +export function cryptoPoolTokenAmountUsdc( + poolAddress: Address, + tokenAddress: Address, + tokenIdx: BigInt, + block: ethereum.Block | null = null +): BigDecimal { + const poolContract = CurvePoolContract.bind(poolAddress); + + const tokenBalance = utils + .readValue( + poolContract.try_balances(tokenIdx), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + const tokenDecimals = utils.getTokenDecimals(tokenAddress); + const tokenPrice = getPriceUsdcRecommended(tokenAddress, block); + const tokenValueUsdc = tokenBalance + .times(tokenPrice.usdPrice) + .div(constants.BIGINT_TEN.pow(tokenDecimals.toI32() as u8).toBigDecimal()); + + return tokenValueUsdc; +} + +export function cryptoPoolUnderlyingTokensAddressesByPoolAddress( + poolAddress: Address +): Address[] { + const poolContract = CurvePoolContract.bind(poolAddress); + + let idx = 0; + const coins: Address[] = []; + while (idx >= 0) { + const coin = utils.readValue
( + poolContract.try_coins(BigInt.fromI32(idx)), + constants.NULL.TYPE_ADDRESS + ); + + if (coin.equals(constants.NULL.TYPE_ADDRESS)) { + return coins; + } + + coins.push(coin); + idx += 1; + } + + return coins; +} + +export function getPriceUsdc( + tokenAddress: Address, + block: ethereum.Block +): CustomPriceType { + if (isCurveLpToken(tokenAddress, block)) + return getCurvePriceUsdc(tokenAddress, block); + + const poolContract = CurvePoolContract.bind(tokenAddress); + const virtualPrice = utils + .readValue( + poolContract.try_get_virtual_price(), + constants.BIGINT_ZERO + ) + .toBigDecimal(); + + const coins: Address[] = []; + for (let i = 0; i < 8; i++) { + const coin = utils.readValue
( + poolContract.try_coins(BigInt.fromI32(i)), + constants.NULL.TYPE_ADDRESS + ); + + coins.push(coin); + } + + const preferredCoin = getPreferredCoinFromCoins(coins); + const price = getPriceUsdcRecommended(preferredCoin, block); + + return CustomPriceType.initialize( + price.usdPrice.times(virtualPrice), + constants.DEFAULT_DECIMALS.toI32() as u8, + constants.OracleType.CURVE_ROUTER + ); +} + +function getLpTokenLiquidityUsdc( + lpAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const poolAddress = getPoolFromLpToken(lpAddress, block); + const poolContract = CurvePoolContract.bind(poolAddress); + + let liquidity = constants.BIGDECIMAL_ZERO; + for (let i = 0; i < 8; i++) { + const coin = utils.readValue
( + poolContract.try_coins(BigInt.fromI32(i)), + constants.NULL.TYPE_ADDRESS + ); + if (coin.equals(constants.NULL.TYPE_ADDRESS) || coin.equals(lpAddress)) + continue; + + const decimals = utils.getTokenDecimals(coin); + const balance = utils.readValue( + poolContract.try_balances(BigInt.fromI32(i as i32)), + constants.BIGINT_ZERO + ); + + const price = getPriceUsdcRecommended(coin, block); + liquidity = liquidity.plus( + balance + .div(constants.BIGINT_TEN.pow(decimals.toI32() as u8)) + .toBigDecimal() + .times(price.usdPrice) + ); + } + + return CustomPriceType.initialize( + liquidity, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.CURVE_ROUTER + ); +} diff --git a/subgraphs/mantle-staked-eth/src/prices/routers/UniswapForksRouter.ts b/subgraphs/mantle-staked-eth/src/prices/routers/UniswapForksRouter.ts new file mode 100644 index 0000000000..16a7a06ed2 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/prices/routers/UniswapForksRouter.ts @@ -0,0 +1,293 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import * as utils from "../common/utils"; +import * as constants from "../common/constants"; +import { CustomPriceType } from "../common/types"; +import { Address, BigInt, ethereum, log } from "@graphprotocol/graph-ts"; +import { UniswapPair as UniswapPairContract } from "../../../generated/Staking/UniswapPair"; +import { UniswapRouter as UniswapRouterContract } from "../../../generated/Staking/UniswapRouter"; +import { UniswapFactory as UniswapFactoryContract } from "../../../generated/Staking/UniswapFactory"; + +export function isLpToken(tokenAddress: Address, ethAddress: Address): bool { + if (tokenAddress.equals(ethAddress)) return false; + + const lpToken = UniswapRouterContract.bind(tokenAddress); + const isFactoryAvailable = utils.readValue( + lpToken.try_factory(), + constants.NULL.TYPE_ADDRESS + ); + + if (isFactoryAvailable.equals(constants.NULL.TYPE_ADDRESS)) return false; + + return true; +} + +export function getTokenPriceUSDC( + tokenAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + if (!config) return new CustomPriceType(); + + const ethAddress = config.ethAddress(); + const usdcAddress = config.usdcAddress(); + + if (isLpToken(tokenAddress, ethAddress)) { + return getLpTokenPriceUsdc(tokenAddress, block); + } + return getPriceFromRouterUSDC(tokenAddress, usdcAddress, block); +} + +export function getPriceFromRouterUSDC( + tokenAddress: Address, + usdcAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + return getPriceFromRouter(tokenAddress, usdcAddress, block); +} + +export function getPriceFromRouter( + token0Address: Address, + token1Address: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const config = utils.getConfig(); + + const ethAddress = config.ethAddress(); + const wethAddress = config.wethAddress(); + + // Construct swap path + const path: Address[] = []; + let numberOfJumps: BigInt; + + // Convert ETH address to WETH + if (token0Address == ethAddress) token0Address = wethAddress; + if (token1Address == ethAddress) token1Address = wethAddress; + + const inputTokenIsWeth: bool = + token0Address.equals(wethAddress) || token1Address.equals(wethAddress); + + if (inputTokenIsWeth) { + // Path = [token0, weth] or [weth, token1] + numberOfJumps = BigInt.fromI32(1); + + path.push(token0Address); + path.push(token1Address); + } else { + // Path = [token0, weth, token1] + numberOfJumps = BigInt.fromI32(2); + + path.push(token0Address); + path.push(wethAddress); + path.push(token1Address); + } + + const token0Decimals = utils.getTokenDecimals(token0Address); + const amountIn = constants.BIGINT_TEN.pow(token0Decimals.toI32() as u8); + + const routerAddresses = config.uniswapForks(); + + let routerAddress = constants.NULL.TYPE_ADDRESS; + let amountOut = constants.BIGINT_ZERO; + for (let idx = 0; idx < routerAddresses.length; idx++) { + const router = routerAddresses[idx]; + if (block && router.startBlock.gt(block.number)) continue; + + const uniswapForkRouter = UniswapRouterContract.bind(router.address); + const amountOutArray = uniswapForkRouter.try_getAmountsOut(amountIn, path); + + if (!amountOutArray.reverted) { + routerAddress = router.address; + amountOut = amountOutArray.value[amountOutArray.value.length - 1]; + break; + } + } + + const feeBips = BigInt.fromI32(30); + + const amountOutBigDecimal = amountOut + .times(constants.BIGINT_TEN_THOUSAND) + .div(constants.BIGINT_TEN_THOUSAND.minus(feeBips.times(numberOfJumps))) + .toBigDecimal(); + + const priceFromRouter = CustomPriceType.initialize( + amountOutBigDecimal, + config.usdcTokenDecimals().toI32() as u8, + constants.OracleType.UNISWAP_FORKS_ROUTER + ); + + const routerContract = UniswapRouterContract.bind(routerAddress); + const factoryAddress = utils.readValue( + routerContract.try_factory(), + constants.NULL.TYPE_ADDRESS + ); + if (factoryAddress.equals(constants.NULL.TYPE_ADDRESS)) + return priceFromRouter; + + const factoryContract = UniswapFactoryContract.bind(factoryAddress); + const tokenPair = utils.readValue( + factoryContract.try_getPair(token0Address, wethAddress), + constants.NULL.TYPE_ADDRESS + ); + if (tokenPair.equals(constants.NULL.TYPE_ADDRESS)) return priceFromRouter; + + const liquidityUSD = getLpTokenLiquidityUsdc(tokenPair, wethAddress, block); + priceFromRouter.setLiquidity(liquidityUSD.usdPrice); + + return priceFromRouter; +} + +export function getLpTokenPriceUsdc( + tokenAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const uniSwapPair = UniswapPairContract.bind(tokenAddress); + + const totalLiquidity: CustomPriceType = getLpTokenTotalLiquidityUsdc( + tokenAddress, + block + ); + const totalSupply = utils.readValue( + uniSwapPair.try_totalSupply(), + constants.BIGINT_ZERO + ); + if (totalSupply == constants.BIGINT_ZERO || totalLiquidity.reverted) { + return new CustomPriceType(); + } + + let pairDecimals: number; + const pairDecimalsCall = uniSwapPair.try_decimals(); + + if (pairDecimalsCall.reverted) { + log.warning( + "[UniswapForksRouter] Failed to fetch pair decimals, tokenAddress: {}", + [tokenAddress.toHexString()] + ); + + return new CustomPriceType(); + } else { + pairDecimals = pairDecimalsCall.value; + } + + const pricePerLpTokenUsdc = totalLiquidity.usdPrice + .times(constants.BIGINT_TEN.pow(pairDecimals as u8).toBigDecimal()) + .div(totalSupply.toBigDecimal()); + + return CustomPriceType.initialize( + pricePerLpTokenUsdc, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.UNISWAP_FORKS_ROUTER + ); +} + +export function getLpTokenTotalLiquidityUsdc( + tokenAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const uniSwapPair = UniswapPairContract.bind(tokenAddress); + + const token0Address = utils.readValue
( + uniSwapPair.try_token0(), + constants.NULL.TYPE_ADDRESS + ); + const token1Address = utils.readValue
( + uniSwapPair.try_token1(), + constants.NULL.TYPE_ADDRESS + ); + + if ( + token0Address.equals(constants.NULL.TYPE_ADDRESS) || + token1Address.equals(constants.NULL.TYPE_ADDRESS) + ) { + return new CustomPriceType(); + } + + const token0Decimals = utils.getTokenDecimals(token0Address); + const token1Decimals = utils.getTokenDecimals(token1Address); + + const reservesCall = uniSwapPair.try_getReserves(); + + if (reservesCall.reverted) return new CustomPriceType(); + + const token0Price = getTokenPriceUSDC(token0Address, block); + const token1Price = getTokenPriceUSDC(token1Address, block); + + if (token0Price.reverted || token1Price.reverted) { + return new CustomPriceType(); + } + + const reserves = reservesCall.value; + const reserve0 = reserves.value0; + const reserve1 = reserves.value1; + + if ( + reserve0.notEqual(constants.BIGINT_ZERO) || + reserve1.notEqual(constants.BIGINT_ZERO) + ) { + const liquidity0 = reserve0 + .toBigDecimal() + .div( + constants.BIGINT_TEN.pow(token0Decimals.toI32() as u8).toBigDecimal() + ) + .times(token0Price.usdPrice); + + const liquidity1 = reserve1 + .toBigDecimal() + .div( + constants.BIGINT_TEN.pow(token1Decimals.toI32() as u8).toBigDecimal() + ) + .times(token1Price.usdPrice); + + const totalLiquidity = liquidity0.plus(liquidity1); + + return CustomPriceType.initialize( + totalLiquidity, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.UNISWAP_FORKS_ROUTER + ); + } + return new CustomPriceType(); +} + +function getLpTokenLiquidityUsdc( + lpAddress: Address, + wethAddress: Address, + block: ethereum.Block | null = null +): CustomPriceType { + const uniSwapPair = UniswapPairContract.bind(lpAddress); + + const token1Call = uniSwapPair.try_token1(); + if (token1Call.reverted) return new CustomPriceType(); + const token1Address = token1Call.value; + + const reservesCall = uniSwapPair.try_getReserves(); + if (reservesCall.reverted) return new CustomPriceType(); + const reserves = reservesCall.value; + + let wethReserves = reserves.value0; + if (token1Address == wethAddress) { + wethReserves = reserves.value1; + } + + const wethPrice = getTokenPriceUSDC(wethAddress, block); + if (wethPrice.reverted) { + return new CustomPriceType(); + } + + const wethDecimals = utils.getTokenDecimals(wethAddress); + + if (wethReserves.notEqual(constants.BIGINT_ZERO)) { + const liquidityUSDC = utils + .bigIntToBigDecimal( + wethReserves, + wethDecimals.toI32() - constants.DEFAULT_USDC_DECIMALS + ) + .times(wethPrice.usdPrice); + + return CustomPriceType.initialize( + liquidityUSDC, + constants.DEFAULT_USDC_DECIMALS, + constants.OracleType.UNISWAP_FORKS_ROUTER + ); + } + return new CustomPriceType(); +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/README.md b/subgraphs/mantle-staked-eth/src/sdk/README.md new file mode 100644 index 0000000000..c8e3bddd41 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/README.md @@ -0,0 +1,25 @@ +## Wat dis? + +This folder contains a library which abstracts the developer from most of the _schema related_ functionality. Ideally, building a subgraph should only consist on understading a protocol and translating certain events into actions and metrics. But the particularities of how this metrics are stored and how they relate to each other inside the schema can and should be abstracted away. Things like taking snapshots of entities every X amount of time, updating TVL every time there is a deposit/withdrawal, updating fees at _swap & pool & protocol & snapshot levels_, etc ... This library aims to do that. + +When using this library, entities should not be updated directly, but always through the library unless absolutely needed. An exception to this is if you create your own auxiliary entity to aid on the handling of some events. + +## How It's Organized + +Pretty straightforward, `/protocols` and `/util`. + +Because we are currently experimenting different approaches, each protocol type has a different schema, and AssemblyScript has some limitations when dealing with interfaces, so far, each protocol type has its own implementation. They all live in their respective folders under `/protocols`, and have very little shared code. + +`/util` contains all these common functions and constants we use over and over. + +## Setting it up + +It would be ideal to have the library to get setup automatically with the messari-cli, in a similar way as we do to generate versions. We might eventually get there, but so far it consists on a manual copy process. + +Refer to each protocol type readme, since requirements might vary: + +- [Bridges](./protocols/bridge/README.md) +- [Lending](./protocols/lending/README.md) +- [Perpetual Futures](./protocols/perpfutures/README.md) +- DEX +- Yield diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/config.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/config.ts new file mode 100644 index 0000000000..f75004e165 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/config.ts @@ -0,0 +1,45 @@ +import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; +import { Token } from "../../../generated/schema"; +import { Versions } from "../../../../../deployment/context/interface"; + +export interface ProtocolConfigurer { + getID(): string; + getName(): string; + getSlug(): string; + getVersions(): Versions; +} + +export class ProtocolConfig implements ProtocolConfigurer { + id: string; + name: string; + slug: string; + versions: Versions; + + constructor(id: string, name: string, slug: string, versions: Versions) { + this.id = id; + this.name = name; + this.slug = slug; + this.versions = versions; + } + + getID(): string { + return this.id; + } + + getName(): string { + return this.name; + } + + getSlug(): string { + return this.slug; + } + + getVersions(): Versions { + return this.versions; + } +} + +export interface TokenPricer { + getTokenPrice(token: Token): BigDecimal; + getAmountValueUSD(token: Token, amount: BigInt): BigDecimal; +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/account.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/account.ts new file mode 100644 index 0000000000..f26c21faa2 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/account.ts @@ -0,0 +1,93 @@ +import { + Account as AccountSchema, + ActiveAccount, +} from "../../../../generated/schema"; +import { TokenManager } from "./tokens"; +import { ProtocolManager } from "./protocol"; +import { Address } from "@graphprotocol/graph-ts"; +import { CustomEventType, getUnixDays, getUnixHours } from "../../util/events"; + +/** + * This file contains the AccountClass, which does + * the operations on the Account entity. This includes: + * - Creating a new Account + * - Updating an existing Account + * + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +export class AccountManager { + protocol: ProtocolManager; + tokens: TokenManager; + + constructor(protocol: ProtocolManager, tokens: TokenManager) { + this.protocol = protocol; + this.tokens = tokens; + } + + loadAccount(address: Address): Account { + let acc = AccountSchema.load(address.toHexString()); + if (acc) { + return new Account(this.protocol, acc, this.tokens); + } + + acc = new AccountSchema(address.toHexString()); + acc.save(); + + this.protocol.addUser(); + + return new Account(this.protocol, acc, this.tokens); + } +} + +export class AccountWasActive { + hourly: boolean; + daily: boolean; +} + +export class Account { + account: AccountSchema; + event: CustomEventType; + protocol: ProtocolManager; + tokens: TokenManager; + + constructor( + protocol: ProtocolManager, + account: AccountSchema, + tokens: TokenManager + ) { + this.account = account; + this.protocol = protocol; + this.event = protocol.getCurrentEvent(); + this.tokens = tokens; + } + + trackActivity(): void { + const days = getUnixDays(this.event.block); + const hours = getUnixHours(this.event.block); + + const generalHourlyID = `${this.account.id}-hourly-${hours}`; + const generalDailyID = `${this.account.id}-daily-${days}`; + + const generalActivity: AccountWasActive = { + daily: this.isActiveByActivityID(generalDailyID), + hourly: this.isActiveByActivityID(generalHourlyID), + }; + + this.protocol.addActiveUser(generalActivity); + this.protocol.addTransaction(); + } + + private isActiveByActivityID(id: string): boolean { + const dAct = ActiveAccount.load(id); + if (!dAct) { + new ActiveAccount(id).save(); + return true; + } + return false; + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/index.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/index.ts new file mode 100644 index 0000000000..1f3858ccf9 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/index.ts @@ -0,0 +1,71 @@ +import { PoolManager } from "./pool"; +import { AccountManager } from "./account"; +import { ProtocolManager } from "./protocol"; +import { BIGINT_ZERO } from "../../util/constants"; +import { ethereum } from "@graphprotocol/graph-ts"; +import { CustomEventType } from "../../util/events"; +import { TokenManager, TokenInitializer } from "./tokens"; +import { ProtocolConfigurer, TokenPricer } from "../config"; + +/** + * This file contains the SDK class, which initializes + * all managers from event or call. + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + + */ + +export class SDK { + Protocol: ProtocolManager; + Accounts: AccountManager; + Pools: PoolManager; + Tokens: TokenManager; + Pricer: TokenPricer; + + constructor( + config: ProtocolConfigurer, + pricer: TokenPricer, + tokenInitializer: TokenInitializer, + event: CustomEventType + ) { + this.Protocol = ProtocolManager.load(config, pricer, event); + this.Tokens = new TokenManager(this.Protocol, tokenInitializer); + this.Accounts = new AccountManager(this.Protocol, this.Tokens); + this.Pools = new PoolManager(this.Protocol, this.Tokens); + this.Pricer = pricer; + + this.Protocol.sdk = this; + } + + static initializeFromEvent( + config: ProtocolConfigurer, + pricer: TokenPricer, + tokenInitializer: TokenInitializer, + event: ethereum.Event + ): SDK { + const customEvent = CustomEventType.initialize( + event.block, + event.transaction, + event.logIndex, + event + ); + return new SDK(config, pricer, tokenInitializer, customEvent); + } + + static initializeFromCall( + config: ProtocolConfigurer, + pricer: TokenPricer, + tokenInitializer: TokenInitializer, + event: ethereum.Call + ): SDK { + const customEvent = CustomEventType.initialize( + event.block, + event.transaction, + BIGINT_ZERO + ); + return new SDK(config, pricer, tokenInitializer, customEvent); + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/pool.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/pool.ts new file mode 100644 index 0000000000..4f6d3e38bd --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/pool.ts @@ -0,0 +1,433 @@ +import { + sortArrayByReference, + sortBytesArray, + updateArrayAtIndex, +} from "../../util/arrays"; +import { + BIGDECIMAL_ZERO, + BIGINT_ZERO, + RewardTokenType, +} from "../../util/constants"; +import { TokenManager } from "./tokens"; +import { ProtocolManager } from "./protocol"; +import { PoolSnapshot } from "./poolSnapshot"; +import { Pool as PoolSchema, Token } from "../../../../generated/schema"; +import { Bytes, BigDecimal, BigInt, Address } from "@graphprotocol/graph-ts"; + +/** + * This file contains the PoolManager, which is used to + * initialize new pools in the protocol. + * + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +export class PoolManager { + protocol: ProtocolManager; + tokens: TokenManager; + + constructor(protocol: ProtocolManager, tokens: TokenManager) { + this.protocol = protocol; + this.tokens = tokens; + } + + loadPool(id: Bytes): Pool { + let entity = PoolSchema.load(id); + if (entity) { + return new Pool(this.protocol, entity, this.tokens, true); + } + + entity = new PoolSchema(id); + entity.protocol = this.protocol.getBytesID(); + + const pool = new Pool(this.protocol, entity, this.tokens, false); + pool.isInitialized = false; + return pool; + } +} + +export class Pool { + pool: PoolSchema; + protocol: ProtocolManager; + tokens: TokenManager; + snapshoter: PoolSnapshot | null = null; + + public isInitialized: boolean = true; + + constructor( + protocol: ProtocolManager, + pool: PoolSchema, + tokens: TokenManager, + isInitialized: bool + ) { + this.pool = pool; + this.protocol = protocol; + this.tokens = tokens; + + if (isInitialized) { + this.snapshoter = new PoolSnapshot(pool, protocol.event); + this.pool.lastUpdateTimestamp = protocol.event.block.timestamp; + this.save(); + } + } + + private save(): void { + this.pool.save(); + } + + initialize( + name: string, + symbol: string, + inputTokens: Bytes[], + outputToken: Token | null, + isLiquidity: bool + ): void { + if (this.isInitialized) { + return; + } + + const event = this.protocol.getCurrentEvent(); + this.pool.protocol = this.protocol.getBytesID(); + this.pool.name = name; + this.pool.symbol = symbol; + this.pool.inputTokens = inputTokens; + this.pool.outputToken = outputToken ? outputToken.id : null; + this.pool.isLiquidityToken = isLiquidity ? true : false; + this.pool.createdTimestamp = event.block.timestamp; + this.pool.createdBlockNumber = event.block.number; + + let inputTokenBalances: BigInt[] = []; + let inputTokenBalancesUSD: BigDecimal[] = []; + for (let i = 0; i < inputTokens.length; i++) { + inputTokenBalances.push(BIGINT_ZERO); + inputTokenBalancesUSD.push(BIGDECIMAL_ZERO); + } + this.pool.inputTokenBalances = inputTokenBalances; + this.pool.inputTokenBalancesUSD = inputTokenBalancesUSD; + this.pool.totalValueLockedUSD = BIGDECIMAL_ZERO; + this.pool.cumulativeSupplySideRevenueUSD = BIGDECIMAL_ZERO; + this.pool.cumulativeProtocolSideRevenueUSD = BIGDECIMAL_ZERO; + this.pool.cumulativeTotalRevenueUSD = BIGDECIMAL_ZERO; + + this.pool.lastSnapshotDayID = 0; + this.pool.lastSnapshotHourID = 0; + this.pool.lastUpdateTimestamp = BIGINT_ZERO; + this.save(); + + this.protocol.addPool(); + } + + /** + * Recalculates the total value locked for this pool based on its current input token balance. + * This function will also update the protocol's total value locked based on the change in this pool's. + */ + private refreshTotalValueLocked(): void { + let totalValueLockedUSD = BIGDECIMAL_ZERO; + + for (let idx = 0; idx < this.pool.inputTokens.length; idx++) { + const inputTokenBalanceUSD = this.pool.inputTokenBalancesUSD[idx]; + totalValueLockedUSD = totalValueLockedUSD.plus(inputTokenBalanceUSD); + } + + this.setTotalValueLocked(totalValueLockedUSD); + } + + /** + * Updates the total value locked for this pool to the given value. + * Will also update the protocol's total value locked based on the change in this pool's. + */ + setTotalValueLocked(newTVL: BigDecimal): void { + const delta = newTVL.minus(this.pool.totalValueLockedUSD); + this.addTotalValueLocked(delta); + this.save(); + } + + /** + * Adds the given delta to the total value locked for this pool. + * Will also update the protocol's total value locked based on the change in this pool's. + * + * @param delta The change in total value locked for this pool. + */ + addTotalValueLocked(delta: BigDecimal): void { + this.pool.totalValueLockedUSD = this.pool.totalValueLockedUSD.plus(delta); + this.protocol.addTotalValueLocked(delta); + this.save(); + } + + /** + * Utility function to update token price. + * + * @param token + * @returns + */ + setTokenPrice(token: Token): void { + if ( + !token.lastPriceBlockNumber || + (token.lastPriceBlockNumber && + token.lastPriceBlockNumber! < this.protocol.event.block.number) + ) { + const pricePerToken = this.protocol.getTokenPricer().getTokenPrice(token); + token.lastPriceUSD = pricePerToken; + token.lastPriceBlockNumber = this.protocol.event.block.number; + token.save(); + } + } + + /** + * Utility function to convert some amount of input token to USD. + * + * @param token + * @param amount the amount of inputToken to convert to USD + * @returns The converted amount. + */ + getInputTokenAmountPrice(token: Token, amount: BigInt): BigDecimal { + this.setTokenPrice(token); + + return this.protocol.getTokenPricer().getAmountValueUSD(token, amount); + } + + /** + * Adds the given amount to the pool's input token balance. It will optionally + * update the pool's and protocol's total value locked. If not stated, will default to true. + * + * @param amount amount to be added to the pool's input token balance. + * @param updateMetrics optional parameter to indicate whether to update the pool's and protocol's total value locked. + */ + addInputTokenBalances( + amounts: BigInt[], + updateMetrics: boolean = true + ): void { + if (amounts.length != this.pool.inputTokenBalances.length) return; + + const newBalances: BigInt[] = []; + for (let idx = 0; idx < this.pool.inputTokenBalances.length; idx++) { + newBalances[idx] = this.pool.inputTokenBalances[idx].plus(amounts[idx]); + } + this.setInputTokenBalances(newBalances, updateMetrics); + } + + /** + * Sets the pool's input token balance to the given amount. It will optionally + * update the pool's and protocol's total value locked. If not stated, will default to true. + * + * @param amount amount to be set as the pool's input token balance. + * @param updateMetrics optional parameter to indicate whether to update the pool's and protocol's total value locked. + */ + setInputTokenBalances( + newBalances: BigInt[], + updateMetrics: boolean = true + ): void { + this.pool.inputTokenBalances = newBalances; + this.setInputTokenBalancesUSD(); + if (updateMetrics) { + this.refreshTotalValueLocked(); + } + } + + /** + * Sets the pool's input token balance USD by calculating it for each token. + */ + private setInputTokenBalancesUSD(): void { + const inputTokenBalancesUSD: BigDecimal[] = []; + for (let idx = 0; idx < this.pool.inputTokens.length; idx++) { + const inputTokenBalance = this.pool.inputTokenBalances[idx]; + const inputToken = this.tokens.getOrCreateToken( + Address.fromBytes(this.pool.inputTokens[idx]) + ); + + const amountUSD = this.getInputTokenAmountPrice( + inputToken, + inputTokenBalance + ); + inputTokenBalancesUSD.push(amountUSD); + } + this.pool.inputTokenBalancesUSD = inputTokenBalancesUSD; + } + + getBytesID(): Bytes { + return this.pool.id; + } + + /** + * Adds a given USD value to the pool and protocol supplySideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param rev {BigDecimal} The value to add to the protocol's supplySideRevenue. + */ + addSupplySideRevenueUSD(rev: BigDecimal): void { + this.pool.cumulativeTotalRevenueUSD = + this.pool.cumulativeTotalRevenueUSD.plus(rev); + this.pool.cumulativeSupplySideRevenueUSD = + this.pool.cumulativeSupplySideRevenueUSD.plus(rev); + this.save(); + + this.protocol.addSupplySideRevenueUSD(rev); + } + + /** + * Adds a given USD value to the pool and protocol protocolSideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param rev {BigDecimal} The value to add to the protocol's protocolSideRevenue. + */ + addProtocolSideRevenueUSD(rev: BigDecimal): void { + this.pool.cumulativeTotalRevenueUSD = + this.pool.cumulativeTotalRevenueUSD.plus(rev); + this.pool.cumulativeProtocolSideRevenueUSD = + this.pool.cumulativeProtocolSideRevenueUSD.plus(rev); + this.save(); + + this.protocol.addProtocolSideRevenueUSD(rev); + } + + /** + * Adds a given USD value to the pool and protocol's supplySideRevenue and protocolSideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param protocolSide {BigDecimal} The value to add to the protocol's protocolSideRevenue. + * @param supplySide {BigDecimal} The value to add to the protocol's supplySideRevenue. + */ + addRevenueUSD(protocolSide: BigDecimal, supplySide: BigDecimal): void { + this.addSupplySideRevenueUSD(supplySide); + this.addProtocolSideRevenueUSD(protocolSide); + } + + /** + * Convenience method to add revenue denominated in the pool's input token. It converts it to USD + * under the hood and calls addRevenueUSD. + */ + addRevenueNative( + inputToken: Token, + protocolSide: BigInt, + supplySide: BigInt + ): void { + const pricer = this.protocol.pricer; + + const pAmountUSD = pricer.getAmountValueUSD(inputToken, protocolSide); + const sAmountUSD = pricer.getAmountValueUSD(inputToken, supplySide); + this.addRevenueUSD(pAmountUSD, sAmountUSD); + } + + /** + * Adds a given amount to the pool's outputTokenSupply. It should only be used for pools + * of type LIQUIDITY. Or pools that emit some kind of LP token on deposit. + * @param amount + */ + addOutputTokenSupply(amount: BigInt): void { + if (!this.pool.outputTokenSupply) { + this.pool.outputTokenSupply = BIGINT_ZERO; + } + this.pool.outputTokenSupply = this.pool.outputTokenSupply!.plus(amount); + this.save(); + } + + /** + * Sets the pool's outputTokenSupply value. It should only be used for pools + * of type LIQUIDITY. Or pools that emit some kind of LP token on deposit. + * It will also update the outputTokenPriceUSD value. + * @param amount + */ + setOutputTokenSupply(token: Token, amount: BigInt): void { + this.pool.outputTokenSupply = amount; + this.refreshOutputTokenPriceUSD(token); + this.save(); + } + + /** + * Adds a given amount to the pool's stakedOutputTokenAmount. + * @param amount + * @returns + */ + addStakedOutputTokenAmount(amount: BigInt): void { + if (!this.pool.stakedOutputTokenAmount) { + this.pool.stakedOutputTokenAmount = BIGINT_ZERO; + this.save(); + return; + } + + this.pool.stakedOutputTokenAmount = + this.pool.stakedOutputTokenAmount!.plus(amount); + this.save(); + } + + /** + * Sets the pool's stakedOutputTokenAmount value. + * @param amount + */ + setStakedOutputTokenAmount(amount: BigInt): void { + this.pool.stakedOutputTokenAmount = amount; + this.save(); + } + + /** + * Updates the price of the pool's output token in USD. + * This is automatically called when changing the output token supply via setOutputTokenSupply + * but can be called manually if necessary. + */ + refreshOutputTokenPriceUSD(token: Token): void { + if (!this.pool.outputToken) { + return; + } + const price = this.protocol.pricer.getTokenPrice(token); + + this.pool.outputTokenPriceUSD = price; + this.save(); + } + + /** + * Sets the rewardTokenEmissions and its USD value for a given reward token. + * It will also create the RewardToken entity and add it to the pool rewardTokens array + * if not already present. + * @param type The type of reward token + * @param token The actual token being rewarded + * @param amount The daily amount of reward tokens emitted to this pool + */ + setRewardEmissions( + type: RewardTokenType, + token: Token, + amount: BigInt + ): void { + const rToken = this.tokens.getOrCreateRewardToken(type, token); + const amountUSD = this.protocol.pricer.getAmountValueUSD(token, amount); + + if (!this.pool.rewardTokens) { + this.pool.rewardTokens = [rToken.id]; + this.pool.rewardTokenEmissionsAmount = [amount]; + this.pool.rewardTokenEmissionsUSD = [amountUSD]; + this.save(); + return; + } + + if (this.pool.rewardTokens!.includes(rToken.id)) { + const index = this.pool.rewardTokens!.indexOf(rToken.id); + this.pool.rewardTokenEmissionsAmount = updateArrayAtIndex( + this.pool.rewardTokenEmissionsAmount!, + amount, + index + ); + this.pool.rewardTokenEmissionsUSD = updateArrayAtIndex( + this.pool.rewardTokenEmissionsUSD!, + amountUSD, + index + ); + this.save(); + return; + } + + const tokens = this.pool.rewardTokens!.concat([rToken.id]); + const newOrder = sortBytesArray(tokens); + + let amounts = this.pool.rewardTokenEmissionsAmount!.concat([amount]); + let amountsUSD = this.pool.rewardTokenEmissionsUSD!.concat([amountUSD]); + amounts = sortArrayByReference(newOrder, tokens, amounts); + amountsUSD = sortArrayByReference(newOrder, tokens, amountsUSD); + + this.pool.rewardTokens = tokens; + this.pool.rewardTokenEmissionsAmount = amounts; + this.pool.rewardTokenEmissionsUSD = amountsUSD; + this.save(); + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/poolSnapshot.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/poolSnapshot.ts new file mode 100644 index 0000000000..b8eb287be5 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/poolSnapshot.ts @@ -0,0 +1,154 @@ +import { + Pool as PoolSchema, + PoolDailySnapshot, + PoolHourlySnapshot, +} from "../../../../generated/schema"; +import { SECONDS_PER_DAY, SECONDS_PER_HOUR } from "../../util/constants"; +import { CustomEventType, getUnixDays, getUnixHours } from "../../util/events"; + +/** + * This file contains the PoolSnapshot, which is used to + * make all of the storage changes that occur in the pool daily and hourly snapshots. + * + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +export class PoolSnapshot { + pool: PoolSchema; + event: CustomEventType; + dayID: i32; + hourID: i32; + + constructor(pool: PoolSchema, event: CustomEventType) { + this.pool = pool; + this.event = event; + this.dayID = getUnixDays(event.block); + this.hourID = getUnixHours(event.block); + this.takeSnapshots(); + } + + private takeSnapshots(): void { + if (!this.pool.lastUpdateTimestamp) return; + + const snapshotDayID = + this.pool.lastUpdateTimestamp.toI32() / SECONDS_PER_DAY; + const snapshotHourID = + this.pool.lastUpdateTimestamp.toI32() / SECONDS_PER_HOUR; + + if (snapshotDayID != this.dayID) { + this.takeDailySnapshot(snapshotDayID); + this.pool.lastSnapshotDayID = snapshotDayID; + this.pool.save(); + } + + if (snapshotHourID != this.hourID) { + this.takeHourlySnapshot(snapshotHourID); + this.pool.lastSnapshotHourID = snapshotHourID; + this.pool.save(); + } + } + + private takeHourlySnapshot(hour: i32): void { + const snapshot = new PoolHourlySnapshot(this.pool.id.concatI32(hour)); + const previousSnapshot = PoolHourlySnapshot.load( + this.pool.id.concatI32(this.pool.lastSnapshotHourID) + ); + + snapshot.hours = hour; + snapshot.protocol = this.pool.protocol; + snapshot.pool = this.pool.id; + snapshot.timestamp = this.event.block.timestamp; + snapshot.blockNumber = this.event.block.number; + + // tvl and balances + snapshot.totalValueLockedUSD = this.pool.totalValueLockedUSD; + snapshot.inputTokenBalances = this.pool.inputTokenBalances; + snapshot.inputTokenBalancesUSD = this.pool.inputTokenBalancesUSD; + snapshot.rewardTokenEmissionsAmount = this.pool.rewardTokenEmissionsAmount; + snapshot.rewardTokenEmissionsUSD = this.pool.rewardTokenEmissionsUSD; + + // revenues + snapshot.cumulativeSupplySideRevenueUSD = + this.pool.cumulativeSupplySideRevenueUSD; + snapshot.cumulativeProtocolSideRevenueUSD = + this.pool.cumulativeProtocolSideRevenueUSD; + snapshot.cumulativeTotalRevenueUSD = this.pool.cumulativeTotalRevenueUSD; + + // deltas + let supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD; + let protocolSideRevenueDelta = snapshot.cumulativeProtocolSideRevenueUSD; + let totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD; + + if (previousSnapshot) { + supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD.minus( + previousSnapshot.cumulativeSupplySideRevenueUSD + ); + protocolSideRevenueDelta = + snapshot.cumulativeProtocolSideRevenueUSD.minus( + previousSnapshot.cumulativeProtocolSideRevenueUSD + ); + totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD.minus( + previousSnapshot.cumulativeTotalRevenueUSD + ); + } + snapshot.hourlySupplySideRevenueUSD = supplySideRevenueDelta; + snapshot.hourlyProtocolSideRevenueUSD = protocolSideRevenueDelta; + snapshot.hourlyTotalRevenueUSD = totalRevenueDelta; + + snapshot.save(); + } + + private takeDailySnapshot(day: i32): void { + const snapshot = new PoolDailySnapshot(this.pool.id.concatI32(day)); + const previousSnapshot = PoolDailySnapshot.load( + this.pool.id.concatI32(this.pool.lastSnapshotDayID) + ); + + snapshot.day = day; + snapshot.protocol = this.pool.protocol; + snapshot.pool = this.pool.id; + snapshot.timestamp = this.event.block.timestamp; + snapshot.blockNumber = this.event.block.number; + + // tvl and balances + snapshot.totalValueLockedUSD = this.pool.totalValueLockedUSD; + snapshot.inputTokenBalances = this.pool.inputTokenBalances; + snapshot.inputTokenBalancesUSD = this.pool.inputTokenBalancesUSD; + snapshot.rewardTokenEmissionsAmount = this.pool.rewardTokenEmissionsAmount; + snapshot.rewardTokenEmissionsUSD = this.pool.rewardTokenEmissionsUSD; + + // revenues + snapshot.cumulativeSupplySideRevenueUSD = + this.pool.cumulativeSupplySideRevenueUSD; + snapshot.cumulativeProtocolSideRevenueUSD = + this.pool.cumulativeProtocolSideRevenueUSD; + snapshot.cumulativeTotalRevenueUSD = this.pool.cumulativeTotalRevenueUSD; + + // deltas + let supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD; + let protocolSideRevenueDelta = snapshot.cumulativeProtocolSideRevenueUSD; + let totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD; + + if (previousSnapshot) { + supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD.minus( + previousSnapshot.cumulativeSupplySideRevenueUSD + ); + protocolSideRevenueDelta = + snapshot.cumulativeProtocolSideRevenueUSD.minus( + previousSnapshot.cumulativeProtocolSideRevenueUSD + ); + totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD.minus( + previousSnapshot.cumulativeTotalRevenueUSD + ); + } + snapshot.dailySupplySideRevenueUSD = supplySideRevenueDelta; + snapshot.dailyProtocolSideRevenueUSD = protocolSideRevenueDelta; + snapshot.dailyTotalRevenueUSD = totalRevenueDelta; + + snapshot.save(); + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocol.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocol.ts new file mode 100644 index 0000000000..29cbb2774b --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocol.ts @@ -0,0 +1,259 @@ +import { SDK } from "."; +import { + dataSource, + Address, + Bytes, + BigDecimal, +} from "@graphprotocol/graph-ts"; +import { AccountWasActive } from "./account"; +import * as constants from "../../util/constants"; +import { BIGINT_ZERO } from "../../util/constants"; +import { CustomEventType } from "../../util/events"; +import { ProtocolSnapshot } from "./protocolSnapshot"; +import { ProtocolConfigurer, TokenPricer } from "../config"; +import { Protocol as ProtocolSchema } from "../../../../generated/schema"; +import { Versions } from "../../../../../../deployment/context/interface"; + +/** + * This file contains the ProtocolManager class, which is used to + * make all of the storage changes that occur in a protocol. + * + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +/** + * ProtocolManager is a wrapper around the ProtocolSchema entity that takes care of + * safely and conveniently updating the entity. Updating the Protocol entity using this + * wrapper also takes care of the Financials and Usage snapshots. + */ +export class ProtocolManager { + protocol: ProtocolSchema; + event: CustomEventType; + pricer: TokenPricer; + snapshoter: ProtocolSnapshot; + sdk: SDK | null = null; + /** + * Creates a new Protocol instance. This should only be called by the Protocol.load + * @private + */ + private constructor( + protocol: ProtocolSchema, + pricer: TokenPricer, + event: CustomEventType + ) { + this.protocol = protocol; + this.event = event; + this.pricer = pricer; + this.snapshoter = new ProtocolSnapshot(protocol, event); + this.protocol.lastUpdateTimestamp = event.block.timestamp; + } + + /** + * This is the main function to instantiate a Protocol entity. Most times it is not called directly, but from the SDK initializer. + * + * @param conf {ProtocolConfigurer} An object that implements the ProtocolConfigurer interface, to set some of the protocol's properties + * @param pricer {TokenPricer} An object that implements the TokenPricer interface, to allow the wrapper to access pricing data + * @param event {CustomEventType} The event being handled at a time. + * @returns Protocol + */ + static load( + conf: ProtocolConfigurer, + pricer: TokenPricer, + event: CustomEventType + ): ProtocolManager { + const id = Address.fromString(conf.getID()); + let protocol = ProtocolSchema.load(id); + if (protocol) { + const proto = new ProtocolManager(protocol, pricer, event); + proto.setVersions(conf.getVersions()); + return proto; + } + + protocol = new ProtocolSchema(id); + protocol.name = conf.getName(); + protocol.slug = conf.getSlug(); + protocol.network = dataSource.network().toUpperCase().replace("-", "_"); + protocol.type = constants.ProtocolType.GENERIC; + protocol.totalValueLockedUSD = constants.BIGDECIMAL_ZERO; + protocol.protocolControlledValueUSD = constants.BIGDECIMAL_ZERO; + protocol.cumulativeSupplySideRevenueUSD = constants.BIGDECIMAL_ZERO; + protocol.cumulativeProtocolSideRevenueUSD = constants.BIGDECIMAL_ZERO; + protocol.cumulativeTotalRevenueUSD = constants.BIGDECIMAL_ZERO; + + protocol.cumulativeTransactionCount = 0; + protocol.cumulativeUniqueUsers = 0; + protocol.totalPoolCount = 0; + + protocol.lastSnapshotDayID = 0; + protocol.lastSnapshotHourID = 0; + protocol.lastUpdateTimestamp = BIGINT_ZERO; + + protocol.schemaVersion = conf.getVersions().getSchemaVersion(); + protocol.subgraphVersion = conf.getVersions().getSubgraphVersion(); + protocol.methodologyVersion = conf.getVersions().getMethodologyVersion(); + + const proto = new ProtocolManager(protocol, pricer, event); + proto.save(); + return proto; + } + + /** + * Updates the protocol entity versions. This is called on load to make sure we update the version + * if we've grafted the subgraph. + * + * @param versions {Versions} An object that implements the Versions interface, to get the protocol's versions + */ + private setVersions(versions: Versions): void { + this.protocol.schemaVersion = versions.getSchemaVersion(); + this.protocol.subgraphVersion = versions.getSubgraphVersion(); + this.protocol.methodologyVersion = versions.getMethodologyVersion(); + this.save(); + } + + /** + * This will save the entity to storage. If any other action needs to be performed on + * save, it should be added here. + * It is meant to be used internally. If you need to save the entity from outside the wrapper + * you should probably be using some of the setters instead. + * @private + */ + private save(): void { + this.protocol.save(); + } + + /** + * + * @returns {string} The ID of the protocol entity. + */ + getID(): string { + return this.protocol.id.toHexString(); + } + + /** + * + * @returns {Bytes} The ID of the protocol entity, as Bytes. + */ + getBytesID(): Bytes { + return this.protocol.id; + } + + /** + * + * @returns {CustomEventType} the event currently being handled. + */ + getCurrentEvent(): CustomEventType { + return this.event; + } + + /** + * + * @returns {TokenPricer} The pricer object used by the wrapper. + * @see TokenPricer + */ + getTokenPricer(): TokenPricer { + return this.pricer; + } + + /** + * Sets the TVL in USD for the protocol. Most times this will be called internally by + * other members of the library when TVL changes are made to them. But if the library + * is not well fitted to a given protocol and you need to set the TVL manually, you can + * use this method. + * It will also update the protocol's snapshots. + * @param tvl {BigDecimal} The new total value locked for the protocol. + */ + setTotalValueLocked(tvl: BigDecimal): void { + this.protocol.totalValueLockedUSD = tvl; + this.save(); + } + + /** + * Adds a given USD value to the protocol's TVL. It can be a positive or negative amount. + * Same as for setTotalValueLocked, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param tvl {BigDecimal} The value to add to the protocol's TVL. + */ + addTotalValueLocked(tvl: BigDecimal): void { + this.protocol.totalValueLockedUSD = + this.protocol.totalValueLockedUSD.plus(tvl); + this.save(); + } + + /** + * Adds a given USD value to the protocol supplySideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param rev {BigDecimal} The value to add to the protocol's supplySideRevenue. + */ + addSupplySideRevenueUSD(rev: BigDecimal): void { + this.protocol.cumulativeTotalRevenueUSD = + this.protocol.cumulativeTotalRevenueUSD.plus(rev); + this.protocol.cumulativeSupplySideRevenueUSD = + this.protocol.cumulativeSupplySideRevenueUSD.plus(rev); + this.save(); + } + + /** + * Adds a given USD value to the protocol protocolSideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param rev {BigDecimal} The value to add to the protocol's protocolSideRevenue. + */ + addProtocolSideRevenueUSD(rev: BigDecimal): void { + this.protocol.cumulativeTotalRevenueUSD = + this.protocol.cumulativeTotalRevenueUSD.plus(rev); + this.protocol.cumulativeProtocolSideRevenueUSD = + this.protocol.cumulativeProtocolSideRevenueUSD.plus(rev); + this.save(); + } + + /** + * Adds a given USD value to the protocol's supplySideRevenue and protocolSideRevenue. It can be a positive or negative amount. + * Same as for the rest of setters, this is mostly to be called internally by the library. + * But you can use it if you need to. It will also update the protocol's snapshots. + * @param protocolSide {BigDecimal} The value to add to the protocol's protocolSideRevenue. + * @param supplySide {BigDecimal} The value to add to the protocol's supplySideRevenue. + */ + addRevenueUSD(protocolSide: BigDecimal, supplySide: BigDecimal): void { + this.addSupplySideRevenueUSD(supplySide); + this.addProtocolSideRevenueUSD(protocolSide); + } + + /** + * Adds some value to the cumulativeUniqueUsers counter. If the value is omitted it will default to 1. + * If you are loading accounts with the AccountManager you won't need to use this method. + * @param count {u8} The value to add to the counter. + */ + addUser(count: u8 = 1): void { + this.protocol.cumulativeUniqueUsers += count; + this.save(); + } + + /** + * Will increase the hourly and daily active users counters. These will be reflected + * on the next Usage snapshot whenever it comes up. + */ + addActiveUser(activity: AccountWasActive): void { + this.snapshoter.addActiveUser(activity); + } + + /** + * Increases the totalPoolCount counter by the given value. + * If you are using the PoolManager class you won't need to use this method. + * @param count {u8} The value to add to the counter. + * @see PoolManager + */ + addPool(count: u8 = 1): void { + this.protocol.totalPoolCount += count; + this.save(); + } + + addTransaction(): void { + this.protocol.cumulativeTransactionCount += 1; + this.save(); + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocolSnapshot.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocolSnapshot.ts new file mode 100644 index 0000000000..3dbae5f803 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/protocolSnapshot.ts @@ -0,0 +1,215 @@ +import { + Protocol as ProtocolSchema, + FinancialsDailySnapshot, + UsageMetricsDailySnapshot, + UsageMetricsHourlySnapshot, + _ActivityHelper, +} from "../../../../generated/schema"; +import { AccountWasActive } from "./account"; +import { Bytes } from "@graphprotocol/graph-ts"; +import { SECONDS_PER_DAY, SECONDS_PER_HOUR } from "../../util/constants"; +import { CustomEventType, getUnixDays, getUnixHours } from "../../util/events"; + +const ActivityHelperID = Bytes.fromUTF8("_ActivityHelper"); + +/** + * This file contains the ProtocolSnapshot, which is used to + * make all of the storage changes that occur in the protocol's + * daily and hourly snapshots. + * + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +/** + * Helper class to manage Financials and Usage snapshots. + * It is not meant to be used directly, but rather by the Protocol and Account lib classes. + * Whenever it is instantiated it will check if it is time to take any of the + * dailyFinancials, dailyUsage or hourlyUsage snapshots. + * + * Snapshots are taken in a way that allows the snapshot entity to be immutable. + */ +export class ProtocolSnapshot { + protocol: ProtocolSchema; + event: CustomEventType; + dayID: i32; + hourID: i32; + activityHelper: _ActivityHelper; + + constructor(protocol: ProtocolSchema, event: CustomEventType) { + this.protocol = protocol; + this.event = event; + this.dayID = getUnixDays(event.block); + this.hourID = getUnixHours(event.block); + this.activityHelper = initActivityHelper(); + this.takeSnapshots(); + } + + addActiveUser(activity: AccountWasActive): void { + this.activityHelper.dailyActiveUsers += activity.daily ? 1 : 0; + this.activityHelper.hourlyActiveUsers += activity.hourly ? 1 : 0; + this.activityHelper.save(); + } + + private takeSnapshots(): void { + const snapshotDayID = + this.protocol.lastUpdateTimestamp.toI32() / SECONDS_PER_DAY; + const snapshotHourID = + this.protocol.lastUpdateTimestamp.toI32() / SECONDS_PER_HOUR; + + if (snapshotDayID != this.dayID) { + this.takeFinancialsDailySnapshot(snapshotDayID); + this.takeUsageDailySnapshot(snapshotDayID); + this.protocol.lastSnapshotDayID = snapshotDayID; + this.protocol.save(); + } + + if (snapshotHourID != this.hourID) { + this.takeUsageHourlySnapshot(snapshotHourID); + this.protocol.lastSnapshotHourID = snapshotHourID; + this.protocol.save(); + } + } + + private takeFinancialsDailySnapshot(day: i32): void { + const snapshot = new FinancialsDailySnapshot(Bytes.fromI32(day)); + const previousSnapshot = FinancialsDailySnapshot.load( + Bytes.fromI32(this.protocol.lastSnapshotDayID) + ); + + snapshot.day = day; + snapshot.protocol = this.protocol.id; + snapshot.blockNumber = this.event.block.number; + snapshot.timestamp = this.event.block.timestamp; + + // tvl + snapshot.totalValueLockedUSD = this.protocol.totalValueLockedUSD; + snapshot.protocolControlledValueUSD = + this.protocol.protocolControlledValueUSD; + + // revenues + snapshot.cumulativeSupplySideRevenueUSD = + this.protocol.cumulativeSupplySideRevenueUSD; + snapshot.cumulativeProtocolSideRevenueUSD = + this.protocol.cumulativeProtocolSideRevenueUSD; + snapshot.cumulativeTotalRevenueUSD = + this.protocol.cumulativeTotalRevenueUSD; + + // deltas + let supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD; + let protocolSideRevenueDelta = snapshot.cumulativeProtocolSideRevenueUSD; + let totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD; + + if (previousSnapshot) { + supplySideRevenueDelta = snapshot.cumulativeSupplySideRevenueUSD.minus( + previousSnapshot.cumulativeSupplySideRevenueUSD + ); + protocolSideRevenueDelta = + snapshot.cumulativeProtocolSideRevenueUSD.minus( + previousSnapshot.cumulativeProtocolSideRevenueUSD + ); + totalRevenueDelta = snapshot.cumulativeTotalRevenueUSD.minus( + previousSnapshot.cumulativeTotalRevenueUSD + ); + } + snapshot.dailySupplySideRevenueUSD = supplySideRevenueDelta; + snapshot.dailyProtocolSideRevenueUSD = protocolSideRevenueDelta; + snapshot.dailyTotalRevenueUSD = totalRevenueDelta; + + snapshot.save(); + } + + private takeUsageDailySnapshot(day: i32): void { + const activity = this.activityHelper; + + const snapshot = new UsageMetricsDailySnapshot(Bytes.fromI32(day)); + const previousSnapshot = UsageMetricsDailySnapshot.load( + Bytes.fromI32(this.protocol.lastSnapshotDayID) + ); + + snapshot.protocol = this.protocol.id; + snapshot.day = day; + snapshot.blockNumber = this.event.block.number; + snapshot.timestamp = this.event.block.timestamp; + + // unique users + snapshot.cumulativeUniqueUsers = this.protocol.cumulativeUniqueUsers; + + // daily activity + snapshot.dailyActiveUsers = activity.dailyActiveUsers; + + // transaction counts + snapshot.cumulativeTransactionCount = + this.protocol.cumulativeTransactionCount; + + // misc + snapshot.totalPoolCount = this.protocol.totalPoolCount; + + // deltas + let transactionDelta = snapshot.cumulativeTransactionCount; + + if (previousSnapshot) { + transactionDelta = + snapshot.cumulativeTransactionCount - + previousSnapshot.cumulativeTransactionCount; + } + snapshot.dailyTransactionCount = transactionDelta; + snapshot.save(); + + activity.dailyActiveUsers = 0; + activity.save(); + } + + private takeUsageHourlySnapshot(hour: i32): void { + const activity = this.activityHelper; + + const snapshot = new UsageMetricsHourlySnapshot(Bytes.fromI32(hour)); + const previousSnapshot = UsageMetricsHourlySnapshot.load( + Bytes.fromI32(this.protocol.lastSnapshotHourID) + ); + + snapshot.protocol = this.protocol.id; + snapshot.hour = hour; + snapshot.blockNumber = this.event.block.number; + snapshot.timestamp = this.event.block.timestamp; + + // unique users + snapshot.cumulativeUniqueUsers = this.protocol.cumulativeUniqueUsers; + + // hourly activity + snapshot.hourlyActiveUsers = activity.hourlyActiveUsers; + + // transaction counts + snapshot.cumulativeTransactionCount = + this.protocol.cumulativeTransactionCount; + + // deltas + let transactionDelta = snapshot.cumulativeTransactionCount; + if (previousSnapshot) { + transactionDelta = + snapshot.cumulativeTransactionCount - + previousSnapshot.cumulativeTransactionCount; + } + snapshot.hourlyTransactionCount = transactionDelta; + snapshot.save(); + + activity.hourlyActiveUsers = 0; + activity.save(); + } +} + +function initActivityHelper(): _ActivityHelper { + let helper = _ActivityHelper.load(ActivityHelperID); + if (helper) { + return helper; + } + helper = new _ActivityHelper(ActivityHelperID); + helper.hourlyActiveUsers = 0; + helper.dailyActiveUsers = 0; + + helper.save(); + return helper; +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/tokens.ts b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/tokens.ts new file mode 100644 index 0000000000..799841f558 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/protocols/generic/tokens.ts @@ -0,0 +1,81 @@ +import { ProtocolManager } from "./protocol"; +import { Address, Bytes, log } from "@graphprotocol/graph-ts"; +import { RewardToken, Token } from "../../../../generated/schema"; +import { BIGDECIMAL_ZERO, RewardTokenType } from "../../util/constants"; + +/** + * This file contains the TokenManagerClass, which initializes + * token entities. + * Schema Version: 2.1.1 + * SDK Version: 1.0.1 + * Author(s): + * - @steegecs + * - @shashwatS22 + */ + +export interface TokenInitializer { + getTokenParams(address: Address): TokenParams; +} + +export class TokenParams { + name: string; + symbol: string; + decimals: i32; + + constructor(name: string, symbol: string, decimals: i32) { + this.name = name; + this.symbol = symbol; + this.decimals = decimals; + } +} + +export class TokenManager { + protocol: ProtocolManager; + initializer: TokenInitializer; + + constructor(protocol: ProtocolManager, init: TokenInitializer) { + this.protocol = protocol; + this.initializer = init; + } + + getOrCreateToken(address: Address): Token { + let token = Token.load(address); + if (token) { + return token; + } + + const params = this.initializer.getTokenParams(address); + token = new Token(address); + token.name = params.name; + token.symbol = params.symbol; + token.decimals = params.decimals; + token.lastPriceUSD = BIGDECIMAL_ZERO; + token.save(); + + return token; + } + + getOrCreateRewardToken(type: RewardTokenType, token: Token): RewardToken { + let id = Bytes.empty(); + if (type == RewardTokenType.BORROW) { + id = id.concatI32(0); + } else if (type == RewardTokenType.DEPOSIT) { + id = id.concatI32(1); + } else { + log.error("Unsupported reward token type", []); + log.critical("", []); + } + id = id.concat(token.id); + + let rToken = RewardToken.load(id); + if (rToken) { + return rToken; + } + + rToken = new RewardToken(id); + rToken.type = type; + rToken.token = token.id; + rToken.save(); + return rToken; + } +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/util/arrays.ts b/subgraphs/mantle-staked-eth/src/sdk/util/arrays.ts new file mode 100644 index 0000000000..d610008f16 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/util/arrays.ts @@ -0,0 +1,103 @@ +import { Bytes } from "@graphprotocol/graph-ts"; + +// A function which given 3 arrays of arbitrary types of the same length, +// where the first one holds the reference order, the second one holds the same elements +// as the first but in different order, and the third any arbitrary elements. It will return +// the third array after sorting it according to the order of the first one. +// For example: +// sortArrayByReference(['a', 'c', 'b'], ['a', 'b', 'c'], [1, 2, 3]) => [1, 3, 2] +export function sortArrayByReference( + reference: T[], + array: T[], + toSort: K[] +): K[] { + const sorted: K[] = new Array(); + for (let i = 0; i < reference.length; i++) { + const index = array.indexOf(reference[i]); + sorted.push(toSort[index]); + } + return sorted; +} + +// sortBytesArray will sort an array of Bytes in ascending order +// by comparing their hex string representation. +export function sortBytesArray(array: Bytes[]): Bytes[] { + const toSort = array.map((item) => item.toHexString()); + toSort.sort(); + return toSort.map((item) => Bytes.fromHexString(item)); +} + +export function updateArrayAtIndex(x: T[], item: T, index: i32): T[] { + if (x.length == 0) { + return [item]; + } + if (index == -1 || index > x.length) { + index = x.length; + } + const retval = new Array(); + let i = 0; + while (i < index) { + retval.push(x[i]); + i += 1; + } + retval.push(item); + i += 1; + while (i < x.length) { + retval.push(x[i]); + i += 1; + } + return retval; +} + +export function addToArrayAtIndex(x: T[], item: T, index: i32 = -1): T[] { + if (x.length == 0) { + return [item]; + } + if (index == -1 || index > x.length) { + index = x.length; + } + const retval = new Array(); + let i = 0; + while (i < index) { + retval.push(x[i]); + i += 1; + } + retval.push(item); + while (i < x.length) { + retval.push(x[i]); + i += 1; + } + return retval; +} + +export function addArrays(a: T[], b: T[]): T[] { + const retval = new Array(); + const arraysByLength = a.length <= b.length ? [a, b] : [b, a]; + + let i = 0; + while (i < arraysByLength[0].length) { + retval.push(a[i].plus(b[i])); + i += 1; + } + while (i < arraysByLength[1].length) { + retval.push(arraysByLength[1][i]); + i += 1; + } + return retval; +} + +export function subtractArrays(a: T[], b: T[]): T[] { + const retval = new Array(); + const arraysByLength = a.length <= b.length ? [a, b] : [b, a]; + + let i = 0; + while (i < arraysByLength[0].length) { + retval.push(a[i].minus(b[i])); + i += 1; + } + while (i < arraysByLength[1].length) { + retval.push(arraysByLength[1][i]); + i += 1; + } + return retval; +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/util/constants.ts b/subgraphs/mantle-staked-eth/src/sdk/util/constants.ts new file mode 100644 index 0000000000..e1cb0a7149 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/util/constants.ts @@ -0,0 +1,229 @@ +import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; + +//////////////////////// +///// Schema Enums ///// +//////////////////////// + +// The network names corresponding to the Network enum in the schema. +// They also correspond to the ones in `dataSource.network()` after converting to lower case. +// See below for a complete list: +// https://thegraph.com/docs/en/hosted-service/what-is-hosted-service/#supported-networks-on-the-hosted-service +export namespace Network { + export const ARBITRUM_ONE = "ARBITRUM_ONE"; + export const AVALANCHE = "AVALANCHE"; + export const AURORA = "AURORA"; + export const BASE = "BASE"; + export const BSC = "BSC"; // aka BNB Chain + export const CELO = "CELO"; + export const MAINNET = "MAINNET"; // Ethereum mainnet + export const FANTOM = "FANTOM"; + export const FUSE = "FUSE"; + export const MOONBEAM = "MOONBEAM"; + export const MOONRIVER = "MOONRIVER"; + export const NEAR_MAINNET = "NEAR_MAINNET"; + export const OPTIMISM = "OPTIMISM"; + export const MATIC = "MATIC"; // aka Polygon + export const XDAI = "XDAI"; // aka Gnosis Chain + + // other networks + export const UBIQ = "UBIQ"; + export const SONGBIRD = "SONGBIRD"; + export const ELASTOS = "ELASTOS"; + export const KARDIACHAIN = "KARDIACHAIN"; + export const CRONOS = "CRONOS"; + export const RSK = "RSK"; + export const TELOS = "TELOS"; + export const XDC = "XDC"; + export const ZYX = "ZYX"; + export const CSC = "CSC"; + export const SYSCOIN = "SYSCOIN"; + export const GOCHAIN = "GOCHAIN"; + export const ETHEREUMCLASSIC = "ETHEREUMCLASSIC"; + export const OKEXCHAIN = "OKEXCHAIN"; + export const HOO = "HOO"; + export const METER = "METER"; + export const NOVA_NETWORK = "NOVA_NETWORK"; + export const TOMOCHAIN = "TOMOCHAIN"; + export const VELAS = "VELAS"; + export const THUNDERCORE = "THUNDERCORE"; + export const HECO = "HECO"; + export const XDAIARB = "XDAIARB"; + export const ENERGYWEB = "ENERGYWEB"; + export const HPB = "HPB"; + export const BOBA = "BOBA"; + export const KUCOIN = "KUCOIN"; + export const SHIDEN = "SHIDEN"; + export const THETA = "THETA"; + export const SX = "SX"; + export const CANDLE = "CANDLE"; + export const ASTAR = "ASTAR"; + export const CALLISTO = "CALLISTO"; + export const WANCHAIN = "WANCHAIN"; + export const METIS = "METIS"; + export const ULTRON = "ULTRON"; + export const STEP = "STEP"; + export const DOGECHAIN = "DOGECHAIN"; + export const RONIN = "RONIN"; + export const KAVA = "KAVA"; + export const IOTEX = "IOTEX"; + export const XLC = "XLC"; + export const NAHMII = "NAHMII"; + export const TOMBCHAIN = "TOMBCHAIN"; + export const CANTO = "CANTO"; + export const KLAYTN = "KLAYTN"; + export const EVMOS = "EVMOS"; + export const SMARTBCH = "SMARTBCH"; + export const BITGERT = "BITGERT"; + export const FUSION = "FUSION"; + export const OHO = "OHO"; + export const ARB_NOVA = "ARB_NOVA"; + export const OASIS = "OASIS"; + export const REI = "REI"; + export const REICHAIN = "REICHAIN"; + export const GODWOKEN = "GODWOKEN"; + export const POLIS = "POLIS"; + export const KEKCHAIN = "KEKCHAIN"; + export const VISION = "VISION"; + export const HARMONY = "HARMONY"; + export const PALM = "PALM"; + export const CURIO = "CURIO"; + + export const UNKNOWN_NETWORK = "UNKNOWN_NETWORK"; +} +export type Network = string; + +export namespace ProtocolType { + export const EXCHANGE = "EXCHANGE"; + export const LENDING = "LENDING"; + export const YIELD = "YIELD"; + export const BRIDGE = "BRIDGE"; + export const OPTION = "OPTION"; + export const PERPETUAL = "PERPETUAL"; + export const GENERIC = "GENERIC"; +} + +export namespace VaultFeeType { + export const MANAGEMENT_FEE = "MANAGEMENT_FEE"; + export const PERFORMANCE_FEE = "PERFORMANCE_FEE"; + export const DEPOSIT_FEE = "DEPOSIT_FEE"; + export const WITHDRAWAL_FEE = "WITHDRAWAL_FEE"; +} + +export namespace LiquidityPoolFeeType { + export const FIXED_TRADING_FEE = "FIXED_TRADING_FEE"; + export const TIERED_TRADING_FEE = "TIERED_TRADING_FEE"; + export const DYNAMIC_TRADING_FEE = "DYNAMIC_TRADING_FEE"; + export const FIXED_LP_FEE = "FIXED_LP_FEE"; + export const DYNAMIC_LP_FEE = "DYNAMIC_LP_FEE"; + export const FIXED_PROTOCOL_FEE = "FIXED_PROTOCOL_FEE"; + export const DYNAMIC_PROTOCOL_FEE = "DYNAMIC_PROTOCOL_FEE"; +} +export type LiquidityPoolFeeType = string; + +export namespace RewardTokenType { + export const DEPOSIT = "DEPOSIT"; + export const BORROW = "BORROW"; + export const STAKE = "STAKE"; +} +export type RewardTokenType = string; + +export namespace ActivityInterval { + export const HOURLY = "HOURLY"; + export const DAILY = "DAILY"; +} + +export namespace LendingType { + export const CDP = "CDP"; + export const POOLED = "POOLED"; +} + +export namespace RiskType { + export const GLOBAL = "GLOBAL"; + export const ISOLATED = "ISOLATED"; +} + +export namespace InterestRateType { + export const STABLE = "STABLE"; + export const VARIABLE = "VARIABLE"; + export const FIXED_TERM = "FIXED_TERM"; +} + +export namespace InterestRateSide { + export const LENDER = "LENDER"; + export const BORROWER = "BORROWER"; +} + +export namespace UsageType { + export const DEPOSIT = "DEPOSIT"; + export const WITHDRAW = "WITHDRAW"; + export const SWAP = "SWAP"; +} + +export namespace PositionSide { + export const LONG = "LONG"; + export const SHORT = "SHORT"; +} +export type PositionSide = string; + +////////////////////////////// +///// Ethereum Addresses ///// +////////////////////////////// + +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +export const ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + +//////////////////////// +///// Type Helpers ///// +//////////////////////// + +export const DEFAULT_DECIMALS = 18; + +export const USDC_DECIMALS = 6; +export const USDC_DENOMINATOR = BigDecimal.fromString("1000000"); + +export const BIGINT_ZERO = BigInt.fromI32(0); +export const BIGINT_ONE = BigInt.fromI32(1); +export const BIGINT_TWO = BigInt.fromI32(2); +export const BIGINT_TEN = BigInt.fromI32(10); +export const BIGINT_HUNDRED = BigInt.fromI32(100); +export const BIGINT_THOUSAND = BigInt.fromI32(1000); +export const BIGINT_TEN_TO_EIGHTEENTH = BigInt.fromString("10").pow(18); +export const BIGINT_MINUS_ONE = BigInt.fromI32(-1); +export const BIGINT_MAX = BigInt.fromString( + "115792089237316195423570985008687907853269984665640564039457584007913129639935" +); + +export const INT_NEGATIVE_ONE = -1 as i32; +export const INT_ZERO = 0 as i32; +export const INT_ONE = 1 as i32; +export const INT_TWO = 2 as i32; +export const INT_FOUR = 4 as i32; + +export const BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); +export const BIGDECIMAL_ONE = new BigDecimal(BIGINT_ONE); +export const BIGDECIMAL_TWO = new BigDecimal(BIGINT_TWO); +export const BIGDECIMAL_HUNDRED = new BigDecimal(BIGINT_HUNDRED); +export const BIGDECIMAL_MINUS_ONE = new BigDecimal(BIGINT_MINUS_ONE); + +export const MAX_UINT = BigInt.fromI32(2).times(BigInt.fromI32(255)); + +///////////////////// +///// Date/Time ///// +///////////////////// + +export const SECONDS_PER_DAY = 60 * 60 * 24; // 86400 +export const SECONDS_PER_HOUR = 60 * 60; // 3600 +export const SECONDS_PER_DAY_BI = BigInt.fromI32(SECONDS_PER_DAY); +export const SECONDS_PER_HOUR_BI = BigInt.fromI32(SECONDS_PER_HOUR); +export const MS_PER_DAY = new BigDecimal(BigInt.fromI32(24 * 60 * 60 * 1000)); +export const DAYS_PER_YEAR = new BigDecimal(BigInt.fromI32(365)); +export const MS_PER_YEAR = DAYS_PER_YEAR.times( + new BigDecimal(BigInt.fromI32(24 * 60 * 60 * 1000)) +); + +//////////////// +///// Misc ///// +//////////////// + +export const ETH_SYMBOL = "ETH"; +export const ETH_NAME = "Ether"; diff --git a/subgraphs/mantle-staked-eth/src/sdk/util/events.ts b/subgraphs/mantle-staked-eth/src/sdk/util/events.ts new file mode 100644 index 0000000000..5c6ee1e4d1 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/util/events.ts @@ -0,0 +1,70 @@ +import { BigInt, Address, Bytes, ethereum } from "@graphprotocol/graph-ts"; +import { + BIGINT_ZERO, + SECONDS_PER_DAY, + SECONDS_PER_HOUR, + ZERO_ADDRESS, +} from "./constants"; + +export class CustomEventType { + block: ethereum.Block; + transaction: ethereum.Transaction; + logIndex: BigInt; + event: ethereum.Event | null; + + constructor() { + this.block = new ethereum.Block( + Bytes.empty(), + Bytes.empty(), + Bytes.empty(), + Address.fromString(ZERO_ADDRESS), + Bytes.empty(), + Bytes.empty(), + Bytes.empty(), + BIGINT_ZERO, + BIGINT_ZERO, + BIGINT_ZERO, + BIGINT_ZERO, + BIGINT_ZERO, + BIGINT_ZERO, + null, + null + ); + this.transaction = new ethereum.Transaction( + Bytes.empty(), + BIGINT_ZERO, + Address.fromString(ZERO_ADDRESS), + null, + BIGINT_ZERO, + BIGINT_ZERO, + BIGINT_ZERO, + Bytes.empty(), + BIGINT_ZERO + ); + this.logIndex = BIGINT_ZERO; + this.event = null; + } + + static initialize( + block: ethereum.Block, + transaction: ethereum.Transaction, + logIndex: BigInt, + event: ethereum.Event | null = null + ): CustomEventType { + const customEvent = new CustomEventType(); + customEvent.block = block; + customEvent.transaction = transaction; + customEvent.logIndex = logIndex; + customEvent.event = event; + + return customEvent; + } +} + +export function getUnixDays(block: ethereum.Block): i32 { + return block.timestamp.toI32() / SECONDS_PER_DAY; +} + +export function getUnixHours(block: ethereum.Block): i32 { + return block.timestamp.toI32() / SECONDS_PER_HOUR; +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/util/numbers.ts b/subgraphs/mantle-staked-eth/src/sdk/util/numbers.ts new file mode 100644 index 0000000000..725140a922 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/util/numbers.ts @@ -0,0 +1,61 @@ +import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; +import { + BIGDECIMAL_ZERO, + BIGINT_TEN, + DEFAULT_DECIMALS, + INT_TWO, +} from "./constants"; + +export function bigIntToBigDecimal( + quantity: BigInt, + decimals: i32 = DEFAULT_DECIMALS +): BigDecimal { + return quantity.divDecimal(BIGINT_TEN.pow(decimals as u8).toBigDecimal()); +} + +export function bigDecimalToBigInt(input: BigDecimal): BigInt { + const str = input.truncate(0).toString(); + return BigInt.fromString(str); +} + +// returns 10^exp +export function exponentToBigDecimal(exp: i32 = DEFAULT_DECIMALS): BigDecimal { + let bd = BigDecimal.fromString("1"); + const ten = BigDecimal.fromString("10"); + for (let i = 0; i < exp; i++) { + bd = bd.times(ten); + } + return bd; +} + +export function calculateAverage(prices: BigDecimal[]): BigDecimal { + let sum = BigDecimal.fromString("0"); + for (let i = 0; i < prices.length; i++) { + sum = sum.plus(prices[i]); + } + + return sum.div( + BigDecimal.fromString(BigInt.fromI32(prices.length).toString()) + ); +} + +export function calculateMedian(prices: BigDecimal[]): BigDecimal { + const sorted = prices.sort((a, b) => { + return a.equals(b) ? 0 : a.gt(b) ? 1 : -1; + }); + + const mid = Math.ceil(sorted.length / 2) as i32; + if (sorted.length % 2 == 0) { + return sorted[mid] + .plus(sorted[mid - 1]) + .div(BigDecimal.fromString(INT_TWO.toString())); + } + + return sorted[mid - 1]; +} + +export function safeDivide(a: BigDecimal, b: BigDecimal): BigDecimal { + if (b == BIGDECIMAL_ZERO) return BIGDECIMAL_ZERO; + + return a.div(b); +} diff --git a/subgraphs/mantle-staked-eth/src/sdk/util/rewards.ts b/subgraphs/mantle-staked-eth/src/sdk/util/rewards.ts new file mode 100644 index 0000000000..ad68987205 --- /dev/null +++ b/subgraphs/mantle-staked-eth/src/sdk/util/rewards.ts @@ -0,0 +1,295 @@ +///////////////////// +// VERSION 1.0.3 //// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// The purpose of this program is to dynamically estimate the blocks generated for the 24 HR period following the most recent update. // +// It does so by calculating the moving average block rate for an arbitrary length of time preceding the current block. // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +import { log, BigDecimal, BigInt, dataSource } from "@graphprotocol/graph-ts"; +import { _CircularBuffer } from "../../../generated/schema"; +import { + Network, + BIGDECIMAL_ZERO, + INT_FOUR, + INT_NEGATIVE_ONE, + INT_ONE, + INT_TWO, + INT_ZERO, +} from "./constants"; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WINDOW_SIZE_SECONDS, TIMESTAMP_STORAGE_INTERVALS, and BUFFER_SIZE can be modified. These are just recommended values - 'somewhat' arbitrary. // +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// The storage interval tells you to only store blocks where the timestamps are separated by at least this amount. +// Increasing this value will mean less blocks stored and less frequently computes blocksSpeed. +export const TIMESTAMP_STORAGE_INTERVAL = 600; + +// The window size determines the range of blocks that you track from the current block minus the window size. +// Window of block time used to calculate the moving average. +// Increasing means less deviation but also less sensitivity to changing block speeds. +export const WINDOW_SIZE_SECONDS = 86400; + +// BUFFER_SIZE determined the size of the array +// Makes the buffer the maximum amount of blocks that can be stored given the block rate and storage interval +// Recommended value is (RATE_IN_SECODNDS / TIMESTAMP_STORAGE_INTERVAL) * 2 - > Round up to nearest even integer +export const BUFFER_SIZE = 288; + +// Add this entity to the schema. +// type _CircularBuffer @entity { +// " 'CIRCULAR_BUFFER' " +// id: ID! + +// " Array of sorted block numbers sorted continuously " +// blocks: [Int!]! + +// " The index in the blocks array which will be used with the newest block to calculate block speed (Usally set to about a day before newest block) " +// windowStartIndex: Int! + +// " The next index in the blocks array that will be replaced with the newest block " +// nextIndex: Int! + +// " This determines the size of the blocks array. Should be set to contain at least a days worth of blocks according to a 1 day window for measuring speed" +// bufferSize: Int! + +// " The current calculated number of blocks per day based on calculated block speed " +// blocksPerDay: BigDecimal! + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const CIRCULAR_BUFFER = "CIRCULAR_BUFFER"; + +// Describes whether the interval for which rewards are emitted is done by block or timestamp +export namespace RewardIntervalType { + export const BLOCK = "BLOCK"; + export const TIMESTAMP = "TIMESTAMP"; +} + +// Forecast period. This gives you the time period that you want to estimate count of blocks per interval, based on moving average block speed. +// 86400 = 1 Day +export const RATE_IN_SECONDS = 86400; +export const RATE_IN_SECONDS_BD = BigDecimal.fromString( + RATE_IN_SECONDS.toString() +); + +// Estimated seconds per block of the protocol +export const STARTING_BLOCKS_PER_DAY = RATE_IN_SECONDS_BD.div( + getStartingBlockRate() +); + +export const WINDOW_SIZE_SECONDS_BD = BigDecimal.fromString( + WINDOW_SIZE_SECONDS.toString() +); + +// Call this function in event handlers frequently enough so that it calls on blocks frequently enough +/** + * @param {BigInt} currentTimestamp - Timestamp for current event + * @param {BigInt} currentBlockNumber - Block nunmber of current event + * @param {BigInt} rewardRate - Rate of reward emissions per reward interval + * @param {BigInt} rewardType - Describes whether rewards are given per block or timestamp + * @returns {BigDecimal} - Returns estimated blocks for specified rate + */ +export function getRewardsPerDay( + currentTimestamp: BigInt, + currentBlockNumber: BigInt, + rewardRate: BigDecimal, + rewardType: string +): BigDecimal { + const circularBuffer = getOrCreateCircularBuffer(); + + // Create entity for the current block + const currentTimestampI32 = currentTimestamp.toI32(); + const currentBlockNumberI32 = currentBlockNumber.toI32(); + + const blocks = circularBuffer.blocks; + + // Interval between index and the index of the start of the window block + const windowWidth = abs( + circularBuffer.windowStartIndex - circularBuffer.nextIndex + ); + if (windowWidth == INT_ZERO) { + if (circularBuffer.nextIndex >= circularBuffer.bufferSize) { + blocks[INT_ZERO] = currentTimestampI32; + blocks[INT_ONE] = currentBlockNumberI32; + circularBuffer.nextIndex = INT_TWO; + } else { + blocks[circularBuffer.nextIndex] = currentTimestampI32; + blocks[circularBuffer.nextIndex + INT_ONE] = currentBlockNumberI32; + circularBuffer.nextIndex += INT_TWO; + } + + circularBuffer.save(); + + // return because there is only 1 reference point. + if (rewardType == RewardIntervalType.TIMESTAMP) { + return rewardRate.times(RATE_IN_SECONDS_BD); + } else { + return circularBuffer.blocksPerDay.times(rewardRate); + } + } + + // Add current timestamp and block numnber to array if new block is at least X blocks later than previously stored. + // Used to save memory and efficiency on array resizing. + let recentSavedTimestamp: i32; + if (circularBuffer.nextIndex == INT_ZERO) { + recentSavedTimestamp = blocks[circularBuffer.bufferSize - INT_TWO]; + } else { + recentSavedTimestamp = blocks[circularBuffer.nextIndex - INT_TWO]; + } + + if ( + currentTimestampI32 - recentSavedTimestamp <= + TIMESTAMP_STORAGE_INTERVAL + ) { + if (rewardType == RewardIntervalType.TIMESTAMP) { + return rewardRate.times(RATE_IN_SECONDS_BD); + } else { + return circularBuffer.blocksPerDay.times(rewardRate); + } + } + + blocks[circularBuffer.nextIndex] = currentTimestampI32; + blocks[circularBuffer.nextIndex + INT_ONE] = currentBlockNumberI32; + if (circularBuffer.nextIndex >= BUFFER_SIZE - INT_TWO) { + circularBuffer.nextIndex = INT_ZERO; + } else { + circularBuffer.nextIndex += INT_TWO; + } + // The timestamp at the start of the window (default 24 hours in seconds). + const startTimestamp = currentTimestampI32 - WINDOW_SIZE_SECONDS; + + // Make sure to still have 2 blocks to calculate rate (This shouldn't happen past the beginning). + while (true) { + if (circularBuffer.nextIndex > circularBuffer.windowStartIndex) { + if ( + circularBuffer.nextIndex - circularBuffer.windowStartIndex <= + INT_FOUR + ) { + break; + } + } else { + if ( + BUFFER_SIZE - + circularBuffer.windowStartIndex + + circularBuffer.nextIndex <= + INT_FOUR + ) { + break; + } + } + const windowIndexBlockTimestamp = blocks[circularBuffer.windowStartIndex]; + + // Shift the start of the window if the current timestamp moves out of desired rate window + if (windowIndexBlockTimestamp < startTimestamp) { + circularBuffer.windowStartIndex = + circularBuffer.windowStartIndex + INT_TWO; + if (circularBuffer.windowStartIndex >= circularBuffer.bufferSize) { + circularBuffer.windowStartIndex = INT_ZERO; + } + } else { + break; + } + } + + // Wideness of the window in seconds. + const windowSecondsCount = BigDecimal.fromString( + (currentTimestampI32 - blocks[circularBuffer.windowStartIndex]).toString() + ); + + // Wideness of the window in blocks. + const windowBlocksCount = BigDecimal.fromString( + ( + currentBlockNumberI32 - blocks[circularBuffer.windowStartIndex + INT_ONE] + ).toString() + ); + + // Estimate block speed for the window in seconds. + const unnormalizedBlockSpeed = + WINDOW_SIZE_SECONDS_BD.div(windowSecondsCount).times(windowBlocksCount); + + // block speed converted to specified rate. + const normalizedBlockSpeed = RATE_IN_SECONDS_BD.div( + WINDOW_SIZE_SECONDS_BD + ).times(unnormalizedBlockSpeed); + + // Update BlockTracker with new values. + circularBuffer.blocksPerDay = normalizedBlockSpeed; + circularBuffer.blocks = blocks; + + circularBuffer.save(); + + if (rewardType == RewardIntervalType.TIMESTAMP) { + return rewardRate.times(RATE_IN_SECONDS_BD); + } else { + return rewardRate.times(circularBuffer.blocksPerDay); + } +} + +function getOrCreateCircularBuffer(): _CircularBuffer { + let circularBuffer = _CircularBuffer.load(CIRCULAR_BUFFER); + + if (!circularBuffer) { + circularBuffer = new _CircularBuffer(CIRCULAR_BUFFER); + + const blocks = new Array(BUFFER_SIZE); + for (let i = INT_ZERO; i < BUFFER_SIZE; i += INT_TWO) { + blocks[i] = INT_NEGATIVE_ONE; + blocks[i + INT_ONE] = INT_NEGATIVE_ONE; + } + + circularBuffer.blocks = blocks; + circularBuffer.windowStartIndex = INT_ZERO; + circularBuffer.nextIndex = INT_ZERO; + circularBuffer.bufferSize = BUFFER_SIZE; + circularBuffer.blocksPerDay = STARTING_BLOCKS_PER_DAY; + + circularBuffer.save(); + } + + return circularBuffer; +} + +function getStartingBlockRate(): BigDecimal { + // Block rates pulled from google searches - rough estimates + + const network = dataSource.network().toUpperCase().replace("-", "_"); + if (network == Network.MAINNET) { + return BigDecimal.fromString("13.39"); + } else if (network == Network.ARBITRUM_ONE) { + return BigDecimal.fromString("15"); + } else if (network == Network.AURORA) { + return BigDecimal.fromString("1.03"); + } else if (network == Network.BSC) { + return BigDecimal.fromString("5"); + } else if (network == Network.CELO) { + return BigDecimal.fromString("5"); + } else if (network == Network.FANTOM) { + return BigDecimal.fromString("1"); + } else if (network == Network.FUSE) { + return BigDecimal.fromString("1"); + } else if (network == Network.OPTIMISM) { + return BigDecimal.fromString("12.5"); + } else if (network == Network.MATIC) { + return BigDecimal.fromString("2"); + } else if (network == Network.XDAI) { + return BigDecimal.fromString("5"); + } else if (network == Network.MOONBEAM) { + return BigDecimal.fromString("13.39"); + } else if (network == Network.MOONRIVER) { + return BigDecimal.fromString("13.39"); + } else if (network == Network.AVALANCHE) { + return BigDecimal.fromString("13.39"); + } else if (network == Network.CRONOS) { + return BigDecimal.fromString("5.5"); + } else if (network == Network.BASE) { + // assuming same block rate as OPTIMISM + return BigDecimal.fromString("12.5"); + } + + // else if (network == SubgraphNetwork.AVALANCHE) return BigDecimal.fromString("2.5") + // else if (dataSource.network() == "harmony") return BigDecimal.fromString("13.39") + else { + log.warning("getStartingBlockRate(): Network not found", []); + return BIGDECIMAL_ZERO; + } +}