diff --git a/README.md b/README.md index 53d580d..8d66788 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Beefy l2-lxp-liquidity-subgraph Subgraph +# Beefy beefy-balances Subgraph This Subgraph sources events from the Beefy contracts in different networks. @@ -8,8 +8,8 @@ This Subgraph sources events from the Beefy contracts in different networks. ### Latest endpoints -- [Linea](https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-l2-lxp-liquidity-linea/latest/gn) -- [Mode](https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-l2-lxp-liquidity-mode/latest/gn) +- [Linea](https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-balances-linea/latest/gn) +- [Mode](https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-balances-mode/latest/gn) # Contributing diff --git a/abis/IERC20/IERC20.json b/abis/IERC20/IERC20.json new file mode 100644 index 0000000..405d6b3 --- /dev/null +++ b/abis/IERC20/IERC20.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/abis/multicall/Multicall3.json b/abis/multicall/Multicall3.json deleted file mode 100644 index bc6b330..0000000 --- a/abis/multicall/Multicall3.json +++ /dev/null @@ -1,440 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "aggregate", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "returnData", - "type": "bytes[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bool", - "name": "allowFailure", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call3[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "aggregate3", - "outputs": [ - { - "components": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Result[]", - "name": "returnData", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bool", - "name": "allowFailure", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call3Value[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "aggregate3Value", - "outputs": [ - { - "components": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Result[]", - "name": "returnData", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "blockAndAggregate", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Result[]", - "name": "returnData", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getBasefee", - "outputs": [ - { - "internalType": "uint256", - "name": "basefee", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "name": "getBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getBlockNumber", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getChainId", - "outputs": [ - { - "internalType": "uint256", - "name": "chainid", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockCoinbase", - "outputs": [ - { - "internalType": "address", - "name": "coinbase", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockDifficulty", - "outputs": [ - { - "internalType": "uint256", - "name": "difficulty", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockGasLimit", - "outputs": [ - { - "internalType": "uint256", - "name": "gaslimit", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockTimestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "getEthBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getLastBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "requireSuccess", - "type": "bool" - }, - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "tryAggregate", - "outputs": [ - { - "components": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Result[]", - "name": "returnData", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "requireSuccess", - "type": "bool" - }, - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "tryBlockAndAggregate", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "returnData", - "type": "bytes" - } - ], - "internalType": "struct Multicall3.Result[]", - "name": "returnData", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/bin/deploy.sh b/bin/deploy.sh index 2e93145..539e4cf 100755 --- a/bin/deploy.sh +++ b/bin/deploy.sh @@ -43,10 +43,10 @@ function publish { PROVIDER=$2 case $PROVIDER in "0xgraph") - publish_0xgraph beefyfinance/l2-lxp-liquidity-$CHAIN-dev + publish_0xgraph beefyfinance/balances-$CHAIN-dev ;; "goldsky") - publish_goldsky beefy-l2-lxp-liquidity-$CHAIN-dev + publish_goldsky beefy-balances-$CHAIN-dev ;; esac } diff --git a/bin/release.sh b/bin/release.sh index 0696d31..117647c 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -36,6 +36,8 @@ function publish_goldsky { DEPLOY_KEY=$3 echo "publishing $SUBGRAPH to goldsky" goldsky subgraph deploy $SUBGRAPH/$VERSION --path . --token $DEPLOY_KEY + sleep 5 # wait for the subgraph to propagate + goldsky subgraph tag create $SUBGRAPH/$VERSION --token $DEPLOY_KEY --tag next } function publish { @@ -46,10 +48,10 @@ function publish { SUBGRAPH= case $PROVIDER in "0xgraph") - publish_0xgraph beefyfinance/l2-lxp-liquidity-$CHAIN $VERSION $DEPLOY_KEY + publish_0xgraph beefyfinance/balances-$CHAIN $VERSION $DEPLOY_KEY ;; "goldsky") - publish_goldsky beefy-l2-lxp-liquidity-$CHAIN $VERSION $DEPLOY_KEY + publish_goldsky beefy-balances-$CHAIN $VERSION $DEPLOY_KEY ;; esac } diff --git a/config/base.json b/config/base.json index 5ec12ab..3ef66b0 100644 --- a/config/base.json +++ b/config/base.json @@ -1,6 +1,7 @@ { "network": "base", "shareTokenMintAddress": "0x0000000000000000000000000000000000000000", + "burnAddress": "0x000000000000000000000000000000000000dead", "clmManagerFactoryAddress": "0x7bC78990AC1ef0754CFdE935B2D84E9acF13ed29", "clmManagerFactoryStartBlock": 15530271, diff --git a/config/linea.json b/config/linea.json index b6beeab..477bd89 100644 --- a/config/linea.json +++ b/config/linea.json @@ -1,6 +1,7 @@ { "network": "linea", "shareTokenMintAddress": "0x0000000000000000000000000000000000000000", + "burnAddress": "0x000000000000000000000000000000000000dead", "clmManagerFactoryAddress": "0x94891042503D4Db76f48D952E7c2cb94453Cd31f", "clmManagerFactoryStartBlock": 5528280, diff --git a/config/mode.json b/config/mode.json index 197f360..fa0e137 100644 --- a/config/mode.json +++ b/config/mode.json @@ -1,6 +1,7 @@ { "network": "mode-mainnet", "shareTokenMintAddress": "0x0000000000000000000000000000000000000000", + "burnAddress": "0x000000000000000000000000000000000000dead", "clmManagerFactoryAddress": "0x0000000000000000000000000000000000000000", "clmManagerFactoryStartBlock": 7558107, diff --git a/package.json b/package.json index 27f7a51..ced509a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "l2-lxp-liquidity-subgraph", + "name": "beefy-balances-subgraph", "private": true, "scripts": { "postinstall": "yarn run --silent prepare:linea && yarn run --silent codegen", diff --git a/schema.graphql b/schema.graphql index fa37076..0ecf46e 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,14 +1,3 @@ -""" -A investor is an address that interacts with our protocol. -""" -type Investor @entity { - "User address" - id: Bytes! - - "The investor's balances in all the tracked vaults" - positions: [InvestorPosition!]! @derivedFrom(field: "investor") -} - """ A token is a representation of a fungible asset on the blockchain as specified by the ERC20 standard. @@ -25,81 +14,21 @@ type Token @entity(immutable: true) { decimals: BigInt! } -enum VaultLifecycle { - "The vault is in the process of being initialized" - INITIALIZING - "The vault is able to accept deposits and earn yield" - RUNNING - "A vault can be paused for a variety of reasons, this is always to protect investors funds" - PAUSED -} - -""" -A Beefy vault -""" -type BeefyVault @entity { - "Vault address" +# account details +type Account @entity { + #account address id: Bytes! - - "Moo token address" - sharesToken: Token! - - "Underlying token address" - underlyingToken: Token! - - "The vault's strategy" - strategy: BeefyStrategy! - - "Technical field to remember if the vault was already initialized" - isInitialized: Boolean! - - "The vault first initialized block number" - initializedAtBlockNumber: BigInt! - - "The vault first initialized timestamp" - initializedAtTimestamp: BigInt! - - "The current lifecycle status of the vault" - lifecycle: VaultLifecycle! - - "The vault's total shares supply as represented in the contract" - rawSharesTokenTotalSupply: BigInt! - - "The vault's total shares supply" - sharesTokenTotalSupply: BigDecimal! - - "positions in the vault" - positions: [InvestorPosition!]! @derivedFrom(field: "vault") + #balances + balances: [TokenBalance!]! @derivedFrom(field: "account") } -""" -A strategy is a contract that manages the assets of a vault. -This is mostly used to start tracking the events and link them to the vault on new event -""" -type BeefyStrategy @entity { - "The strategy address" +# token balance details +type TokenBalance @entity { id: Bytes! - - "The vault the strategy is managing" - vault: BeefyVault! - - "Technical field to remember if the strategy was already initialized" - isInitialized: Boolean! -} - -type InvestorPosition @entity { - "vault address + investor address" - id: Bytes! - - "Investor address" - investor: Investor! - - "Vault address" - vault: BeefyVault! - - "Amount of shares, with maximum precision. Represented with `vault.shareToken.decimals` decimals" - rawSharesBalance: BigInt! - - "Amount of shares" - sharesBalance: BigDecimal! + #token + token: Token! + #account + account: Account! + #amount + amount: BigInt! } diff --git a/src/classic/lifecycle.ts b/src/classic/lifecycle.ts new file mode 100644 index 0000000..179895b --- /dev/null +++ b/src/classic/lifecycle.ts @@ -0,0 +1,31 @@ +import { log } from "@graphprotocol/graph-ts" +import { ProxyCreated as VaultOrStrategyCreated } from "../../generated/ClassicVaultFactory/ClassicVaultFactory" +import { BoostDeployed as BoostCreated } from "../../generated/ClassicBoostFactory/ClassicBoostFactory" +import { ClassicVault as ClassicVaultContract } from "../../generated/ClassicVaultFactory/ClassicVault" +import { BeefyERC20Product as BeefyERC20ProductTemplate } from "../../generated/templates" +import { fetchAndSaveTokenData } from "../common/utils/token" + +export function handleClassicVaultOrStrategyCreated(event: VaultOrStrategyCreated): void { + const address = event.params.proxy + + // test if we are creating a vault or a strategy + const vaultContract = ClassicVaultContract.bind(address) + const vaultStrategyRes = vaultContract.try_strategy() + + // proxy also creates the strategies + if (vaultStrategyRes.reverted) { + log.debug("`strategy()` method does not exist on contract: {}. It's not a vault", [address.toHexString()]) + } else { + log.info("Creating Classic Vault: {}", [address.toHexString()]) + + fetchAndSaveTokenData(address) + BeefyERC20ProductTemplate.create(address) + } +} + +export function handleClassicBoostCreated(event: BoostCreated): void { + // TODO: this is wrong + const address = event.params.boost + fetchAndSaveTokenData(address) + BeefyERC20ProductTemplate.create(address) +} diff --git a/src/classic/mapping/boost-factory.ts b/src/classic/mapping/boost-factory.ts new file mode 100644 index 0000000..71706c4 --- /dev/null +++ b/src/classic/mapping/boost-factory.ts @@ -0,0 +1 @@ +export { handleClassicBoostCreated } from "../lifecycle" diff --git a/src/classic/mapping/vault-and-strategy-factory.ts b/src/classic/mapping/vault-and-strategy-factory.ts new file mode 100644 index 0000000..5c32e60 --- /dev/null +++ b/src/classic/mapping/vault-and-strategy-factory.ts @@ -0,0 +1 @@ +export { handleClassicVaultOrStrategyCreated } from "../lifecycle" diff --git a/src/clm/lifecycle.ts b/src/clm/lifecycle.ts new file mode 100644 index 0000000..515c760 --- /dev/null +++ b/src/clm/lifecycle.ts @@ -0,0 +1,16 @@ +import { ProxyCreated as CLMManagerCreatedEvent } from "../../generated/ClmManagerFactory/ClmManagerFactory" +import { ProxyCreated as RewardPoolCreatedEvent } from "../../generated/RewardPoolFactory/RewardPoolFactory" +import { BeefyERC20Product as BeefyERC20ProductTemplate } from "../../generated/templates" +import { fetchAndSaveTokenData } from "../common/utils/token" + +export function handleClmManagerCreated(event: CLMManagerCreatedEvent): void { + const address = event.params.proxy + fetchAndSaveTokenData(address) + BeefyERC20ProductTemplate.create(address) +} + +export function handleRewardPoolCreated(event: RewardPoolCreatedEvent): void { + const address = event.params.proxy + fetchAndSaveTokenData(address) + BeefyERC20ProductTemplate.create(address) +} diff --git a/src/clm/mapping/manager-factory.ts b/src/clm/mapping/manager-factory.ts new file mode 100644 index 0000000..716a58f --- /dev/null +++ b/src/clm/mapping/manager-factory.ts @@ -0,0 +1 @@ +export { handleClmManagerCreated } from "../lifecycle" diff --git a/src/clm/mapping/reward-pool-factory.ts b/src/clm/mapping/reward-pool-factory.ts new file mode 100644 index 0000000..ee57608 --- /dev/null +++ b/src/clm/mapping/reward-pool-factory.ts @@ -0,0 +1 @@ +export { handleRewardPoolCreated } from "../lifecycle" diff --git a/src/common/entity/account.ts b/src/common/entity/account.ts new file mode 100644 index 0000000..f448744 --- /dev/null +++ b/src/common/entity/account.ts @@ -0,0 +1,11 @@ +import { Bytes } from "@graphprotocol/graph-ts" +import { Account } from "../../../generated/schema" + +export function getAccount(accountAddress: Bytes): Account { + let account = Account.load(accountAddress) + if (!account) { + account = new Account(accountAddress) + } + + return account +} diff --git a/src/common/entity/balance.ts b/src/common/entity/balance.ts new file mode 100644 index 0000000..082aee6 --- /dev/null +++ b/src/common/entity/balance.ts @@ -0,0 +1,15 @@ +import { Account, Token, TokenBalance } from "../../../generated/schema" +import { ZERO_BI } from "../utils/decimal" + +export function getTokenBalance(token: Token, account: Account): TokenBalance { + const id = account.id.concat(token.id) + let tokenBalance = TokenBalance.load(id) + if (!tokenBalance) { + tokenBalance = new TokenBalance(id) + tokenBalance.account = account.id + tokenBalance.token = token.id + tokenBalance.amount = ZERO_BI + } + + return tokenBalance +} diff --git a/src/common/entity/token.ts b/src/common/entity/token.ts new file mode 100644 index 0000000..f34ae5c --- /dev/null +++ b/src/common/entity/token.ts @@ -0,0 +1,33 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { Token } from "../../../generated/schema" +import { ADDRESS_ZERO } from "../utils/address" + +export function isNullToken(token: Token): boolean { + return token.id.equals(ADDRESS_ZERO) +} + +export function getNullToken(): Token { + let token = Token.load(ADDRESS_ZERO) + if (!token) { + token = new Token(ADDRESS_ZERO) + token.symbol = "NULL" + token.name = "NULL" + token.decimals = BigInt.fromI32(18) + token.save() + } + return token +} + +export function getToken(tokenAddress: Bytes): Token { + if (tokenAddress == ADDRESS_ZERO) { + return getNullToken() + } + let token = Token.load(tokenAddress) + if (!token) { + token = new Token(tokenAddress) + token.symbol = "" + token.name = "" + token.decimals = BigInt.fromI32(18) + } + return token +} diff --git a/src/common/interaction.ts b/src/common/interaction.ts new file mode 100644 index 0000000..620f991 --- /dev/null +++ b/src/common/interaction.ts @@ -0,0 +1,26 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { Transfer as TransferEvent } from "../../generated/templates/BeefyERC20Product/IERC20" +import { BURN_ADDRESS, SHARE_TOKEN_MINT_ADDRESS } from "../config" +import { getAccount } from "./entity/account" +import { getTokenBalance } from "./entity/balance" +import { getToken } from "./entity/token" + +export function handleProductTransfer(event: TransferEvent): void { + if (event.params.from.notEqual(SHARE_TOKEN_MINT_ADDRESS) && event.params.from.notEqual(BURN_ADDRESS)) { + updateAccountBalance(event.address, event.params.from, event.params.value.neg()) + } + + if (event.params.to.notEqual(SHARE_TOKEN_MINT_ADDRESS) && event.params.to.notEqual(BURN_ADDRESS)) { + updateAccountBalance(event.address, event.params.to, event.params.value) + } +} + +function updateAccountBalance(tokenAddress: Bytes, accountAddress: Bytes, amountDiff: BigInt): void { + const account = getAccount(accountAddress) + account.save() + const token = getToken(tokenAddress) + token.save() + const balance = getTokenBalance(token, account) + balance.amount = balance.amount.plus(amountDiff) + balance.save() +} diff --git a/src/common/mapping/erc20.ts b/src/common/mapping/erc20.ts new file mode 100644 index 0000000..b34beb4 --- /dev/null +++ b/src/common/mapping/erc20.ts @@ -0,0 +1 @@ +export { handleProductTransfer } from "../interaction" diff --git a/src/utils/address.ts b/src/common/utils/address.ts similarity index 100% rename from src/utils/address.ts rename to src/common/utils/address.ts diff --git a/src/common/utils/decimal.ts b/src/common/utils/decimal.ts new file mode 100644 index 0000000..d0113ca --- /dev/null +++ b/src/common/utils/decimal.ts @@ -0,0 +1,24 @@ +import { BigInt } from "@graphprotocol/graph-ts" + +export let ZERO_BI = BigInt.fromI32(0) +export let ONE_BI = BigInt.fromI32(1) +export let TEN_BI = BigInt.fromI32(10) + +export function changeValueEncoding(value: BigInt, currentDecimalsEncoding: BigInt, requestedDecimalsEcoding: BigInt): BigInt { + if (currentDecimalsEncoding.equals(requestedDecimalsEcoding)) { + return value + } else if (currentDecimalsEncoding.gt(requestedDecimalsEcoding)) { + return value.div(exponentToBigInt(currentDecimalsEncoding.minus(requestedDecimalsEcoding))) + } else { + return value.times(exponentToBigInt(requestedDecimalsEcoding.minus(currentDecimalsEncoding))) + } +} + +function exponentToBigInt(decimals: BigInt): BigInt { + let bd = ONE_BI + let n = decimals.toI32() + for (let i = 0; i < n; i++) { + bd = bd.times(TEN_BI) + } + return bd +} diff --git a/src/common/utils/event.ts b/src/common/utils/event.ts new file mode 100644 index 0000000..46f5b24 --- /dev/null +++ b/src/common/utils/event.ts @@ -0,0 +1,5 @@ +import { Bytes, ethereum } from "@graphprotocol/graph-ts" + +export function getEventIdentifier(event: ethereum.Event): Bytes { + return event.transaction.hash.concat(Bytes.fromByteArray(Bytes.fromBigInt(event.logIndex))) +} diff --git a/src/common/utils/time.ts b/src/common/utils/time.ts new file mode 100644 index 0000000..13a588c --- /dev/null +++ b/src/common/utils/time.ts @@ -0,0 +1,49 @@ +import { BigInt } from "@graphprotocol/graph-ts" +import { log } from "matchstick-as" +import { ZERO_BI } from "./decimal" + +export const MINUTES_15 = BigInt.fromI32(60 * 15) +export const HOUR = BigInt.fromI32(60 * 60) +export const DAY = BigInt.fromI32(60 * 60 * 24) +export const WEEK = BigInt.fromI32(60 * 60 * 24 * 7) +export const MONTH = BigInt.fromI32(60 * 60 * 24 * 30) +export const QUARTER = BigInt.fromI32(60 * 60 * 24 * 30 * 3) +export const YEAR = BigInt.fromI32(60 * 60 * 24 * 365) + +export function getIntervalFromTimestamp(timestamp: BigInt, period: BigInt): BigInt { + // if the period is not stable, use date math to calculate the interval + if (period.ge(WEEK)) { + const date = new Date(timestamp.toI64() * 1000) + date.setUTCMilliseconds(0) + date.setUTCSeconds(0) + date.setUTCMinutes(0) + date.setUTCHours(0) + date.setUTCDate(date.getUTCDate() - date.getUTCDay()) + if (period.equals(WEEK)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCDate(1) + if (period.equals(MONTH)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCMonth(date.getUTCMonth() - (date.getUTCMonth() % 3)) + if (period.equals(QUARTER)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCMonth(0) + if (period.equals(YEAR)) { + return BigInt.fromI64(date.getTime() / 1000) + } + + log.error("Unsupported period: {}", [period.toString()]) + period = DAY + } else if (period.equals(ZERO_BI)) { + return timestamp + } + return timestamp.div(period).times(period) +} + +export function getPreviousIntervalFromTimestamp(timestamp: BigInt, period: BigInt): BigInt { + const truncated = getIntervalFromTimestamp(timestamp, period) + return getIntervalFromTimestamp(truncated.minus(BigInt.fromI32(10)), period) +} diff --git a/src/common/utils/token.ts b/src/common/utils/token.ts new file mode 100644 index 0000000..a1935ec --- /dev/null +++ b/src/common/utils/token.ts @@ -0,0 +1,28 @@ +import { IERC20 as IERC20Contract } from "../../../generated/templates/BeefyERC20Product/IERC20" +import { Token } from "../../../generated/schema" +import { Address, BigInt, Bytes } from "@graphprotocol/graph-ts" +import { getToken } from "../entity/token" + +export function fetchAndSaveTokenData(tokenAddress: Bytes): Token { + const tokenContract = IERC20Contract.bind(Address.fromBytes(tokenAddress)) + // use individual calls as there is a good change other subgraph has requested + // this token's metadata and it's already in the graph-node cache + + // if any of these calls revert, we will just use the default values to avoid a subgraph crash + const tokenDecimalsRes = tokenContract.try_decimals() + const tokenDecimals = tokenDecimalsRes.reverted ? 18 : tokenDecimalsRes.value + + const tokenNameRes = tokenContract.try_name() + const tokenName = tokenNameRes.reverted ? "Unknown" : tokenNameRes.value + + const tokenSymbolRes = tokenContract.try_symbol() + const tokenSymbol = tokenSymbolRes.reverted ? "UNKNOWN" : tokenSymbolRes.value + + const token = getToken(tokenAddress) + token.name = tokenName + token.symbol = tokenSymbol + token.decimals = BigInt.fromI32(tokenDecimals) + token.save() + + return token +} diff --git a/src/config.template.ts b/src/config.template.ts index ff89466..8a0a122 100644 --- a/src/config.template.ts +++ b/src/config.template.ts @@ -1,4 +1,5 @@ import { Address } from "@graphprotocol/graph-ts" export const NETWORK_NAME = "{{network}}" -export const SHARE_TOKEN_MINT_ADDRESS = Address.fromString("{{shareTokenMintAddress}}") \ No newline at end of file +export const SHARE_TOKEN_MINT_ADDRESS = Address.fromString("{{shareTokenMintAddress}}") +export const BURN_ADDRESS = Address.fromString("{{burnAddress}}") \ No newline at end of file diff --git a/src/entity/investor.ts b/src/entity/investor.ts deleted file mode 100644 index 1e6fe92..0000000 --- a/src/entity/investor.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Bytes } from "@graphprotocol/graph-ts" -import { Investor } from "../../generated/schema" - -export function getInvestor(accountAddress: Bytes): Investor { - let investor = Investor.load(accountAddress) - if (!investor) { - investor = new Investor(accountAddress) - } - - return investor -} diff --git a/src/entity/position.ts b/src/entity/position.ts deleted file mode 100644 index 4aa52b1..0000000 --- a/src/entity/position.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Bytes } from "@graphprotocol/graph-ts" -import { BeefyVault, Investor, InvestorPosition } from "../../generated/schema" -import { ZERO_BD, ZERO_BI } from "../utils/decimal" - -// @ts-ignore -@inline -export function getInvestorPositionId(vault: BeefyVault, investor: Investor): Bytes { - return vault.id.concat(investor.id) -} - -export function isNewInvestorPosition(position: InvestorPosition): boolean { - return position.sharesBalance.equals(ZERO_BD) -} - -export function getInvestorPosition(vault: BeefyVault, investor: Investor): InvestorPosition { - let id = getInvestorPositionId(vault, investor) - let position = InvestorPosition.load(id) - if (!position) { - position = new InvestorPosition(id) - position.vault = vault.id - position.investor = investor.id - position.rawSharesBalance = ZERO_BI - position.sharesBalance = ZERO_BD - } - return position -} diff --git a/src/entity/token.ts b/src/entity/token.ts deleted file mode 100644 index ce4ea97..0000000 --- a/src/entity/token.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BigInt, Bytes } from "@graphprotocol/graph-ts" -import { Token } from "../../generated/schema" -import { Multicall3Params, multicall } from "../utils/multicall" - -export function getTokenAndInitIfNeeded(tokenAddress: Bytes): Token { - let token = Token.load(tokenAddress) - if (!token) { - token = new Token(tokenAddress) - - const signatures = [ - new Multicall3Params(tokenAddress, "symbol()", "string"), - new Multicall3Params(tokenAddress, "name()", "string"), - new Multicall3Params(tokenAddress, "decimals()", "uint8"), - ] - - const results = multicall(signatures) - - token.symbol = results[0].value.toString() - token.name = results[1].value.toString() - token.decimals = BigInt.fromI32(results[2].value.toI32()) - token.save() - } - return token -} diff --git a/src/entity/vault.ts b/src/entity/vault.ts deleted file mode 100644 index 108b76c..0000000 --- a/src/entity/vault.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Bytes } from "@graphprotocol/graph-ts" -import { BeefyStrategy, BeefyVault } from "../../generated/schema" -import { ADDRESS_ZERO } from "../utils/address" -import { ZERO_BD, ZERO_BI } from "../utils/decimal" - -export const BEEFY_VAULT_LIFECYCLE_INITIALIZING = "INITIALIZING" -export const BEEFY_VAULT_LIFECYCLE_RUNNING = "RUNNING" -export const BEEFY_VAULT_LIFECYCLE_PAUSED = "PAUSED" - -export function isVaultInitialized(vault: BeefyVault): boolean { - return vault.lifecycle != BEEFY_VAULT_LIFECYCLE_INITIALIZING -} - -export function isVaultRunning(vault: BeefyVault): boolean { - return vault.lifecycle == BEEFY_VAULT_LIFECYCLE_RUNNING -} - -export function isNewVault(vault: BeefyVault): boolean { - return vault.sharesToken.equals(ADDRESS_ZERO) -} - -export function getBeefyVault(vaultAddress: Bytes): BeefyVault { - let vault = BeefyVault.load(vaultAddress) - if (!vault) { - vault = new BeefyVault(vaultAddress) - vault.sharesToken = ADDRESS_ZERO - vault.underlyingToken = ADDRESS_ZERO - vault.strategy = ADDRESS_ZERO - vault.isInitialized = false - vault.initializedAtBlockNumber = ZERO_BI - vault.initializedAtTimestamp = ZERO_BI - vault.lifecycle = BEEFY_VAULT_LIFECYCLE_INITIALIZING - vault.rawSharesTokenTotalSupply = ZERO_BI - vault.sharesTokenTotalSupply = ZERO_BD - } - return vault -} - -export function getBeefyStrategy(strategyAddress: Bytes): BeefyStrategy { - let strategy = BeefyStrategy.load(strategyAddress) - if (!strategy) { - strategy = new BeefyStrategy(strategyAddress) - strategy.vault = ADDRESS_ZERO - strategy.isInitialized = false - } - return strategy -} diff --git a/src/mapping/factory.ts b/src/mapping/factory.ts deleted file mode 100644 index ccd128a..0000000 --- a/src/mapping/factory.ts +++ /dev/null @@ -1 +0,0 @@ -export { handleVaultCreated as handleVaultCreated } from "../vault-lifecycle" diff --git a/src/mapping/strategy.ts b/src/mapping/strategy.ts deleted file mode 100644 index 48e464b..0000000 --- a/src/mapping/strategy.ts +++ /dev/null @@ -1 +0,0 @@ -export { handleStrategyInitialized as handleInitialized } from "../vault-lifecycle" diff --git a/src/mapping/vault.ts b/src/mapping/vault.ts deleted file mode 100644 index 01a6294..0000000 --- a/src/mapping/vault.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { handleVaultInitialized as handleInitialized, handleUpgradeStrat as handleUpgradeStrat } from "../vault-lifecycle" -export { handleVaultTransfer as handleTransfer } from "../vault-interaction" diff --git a/src/utils/decimal.ts b/src/utils/decimal.ts deleted file mode 100644 index c4b4069..0000000 --- a/src/utils/decimal.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { BigDecimal, BigInt } from "@graphprotocol/graph-ts" - -export let ZERO_BI = BigInt.fromI32(0) -export let ONE_BI = BigInt.fromI32(1) -export let TEN_BI = BigInt.fromI32(10) -export let EIGHTEEN_BI = BigInt.fromI32(18) -export let ZERO_BD = BigDecimal.fromString("0") -export let ONE_BD = BigDecimal.fromString("1") -export let TEN_BD = BigDecimal.fromString("10") -export let ONE_ETH_BI = BigInt.fromString("1000000000000000000") -export let ONE_ETH_BD = BigDecimal.fromString("1000000000000000000") -export let ONE_GWEI_BI = BigInt.fromI32(1000000000) -export let ONE_GWEI_BD = BigDecimal.fromString("1000000000") - -/** - * Adapted from uniswap subgraph - * @see https://github.com/Uniswap/v3-subgraph/blob/bf03f940f17c3d32ee58bd37386f26713cff21e2/src/utils/index.ts#L41-L46 - */ -@inline -export function tokenAmountToDecimal(tokenAmount: BigInt, exchangeDecimals: BigInt): BigDecimal { - if (exchangeDecimals == ZERO_BI) { - return tokenAmount.toBigDecimal() - } - return tokenAmount.toBigDecimal().div(exponentToBigDecimal(exchangeDecimals)) -} - - -@inline -export function decimalToTokenAmount(decimal: BigDecimal, exchangeDecimals: BigInt): BigInt { - if (exchangeDecimals == ZERO_BI) { - return BigInt.fromString(decimal.toString()) - } - return BigInt.fromString(decimal.times(exponentToBigDecimal(exchangeDecimals)).toString()) -} - - -@inline -export function weiToBigDecimal(wei: BigInt): BigDecimal { - return tokenAmountToDecimal(wei, BigInt.fromI32(18)) -} - -/** - * Adapted from uniswap subgraph - * @see https://github.com/Uniswap/v3-subgraph/blob/bf03f940f17c3d32ee58bd37386f26713cff21e2/src/utils/index.ts#L6-L12 - */ -@inline -export function exponentToBigDecimal(decimals: BigInt): BigDecimal { - let bd = ONE_BD - let n = decimals.toI32() - for (let i = 0; i < n; i++) { - bd = bd.times(TEN_BD) - } - return bd -} - - -@inline -export function exponentToBigInt(decimals: BigInt): BigInt { - let bd = ONE_BI - let n = decimals.toI32() - for (let i = 0; i < n; i++) { - bd = bd.times(TEN_BI) - } - return bd -} - -/** - * Ver - * @see https://github.com/pancakeswap/pancake-subgraph/blob/e57f6313d5c4e8759f26945a893518d496944d4c/subgraphs/exchange-v3/lite/utils/index.ts#L23-L39 - */ -@inline -export function bigDecimalExponated(value: BigDecimal, power: BigInt): BigDecimal { - if (power.equals(ZERO_BI)) { - return ONE_BD - } - let negativePower = power.lt(ZERO_BI) - let result = ZERO_BD.plus(value) - let powerAbs = power.abs() - for (let i = ONE_BI; i.lt(powerAbs); i = i.plus(ONE_BI)) { - result = result.times(value) - } - - if (negativePower) { - result = safeDiv(ONE_BD, result) - } - - return result -} - -/** - * Adapted from uniswap subgraph - * @see https://github.com/Uniswap/v3-subgraph/blob/bf03f940f17c3d32ee58bd37386f26713cff21e2/src/utils/index.ts#L15-L21 - */ -@inline -function safeDiv(amount0: BigDecimal, amount1: BigDecimal): BigDecimal { - if (amount1.equals(ZERO_BD)) { - return ZERO_BD - } else { - return amount0.div(amount1) - } -} - - -@inline -export function bigDecMin(a: BigDecimal, b: BigDecimal): BigDecimal { - return a.lt(b) ? a : b -} - - -@inline -export function bigDecMax(a: BigDecimal, b: BigDecimal): BigDecimal { - return a.gt(b) ? a : b -} - - -@inline -export function bigIntMax(a: BigInt, b: BigInt): BigInt { - return a.gt(b) ? a : b -} - - -@inline -export function bigIntMin(a: BigInt, b: BigInt): BigInt { - return a.lt(b) ? a : b -} diff --git a/src/utils/multicall.ts b/src/utils/multicall.ts deleted file mode 100644 index fb497e0..0000000 --- a/src/utils/multicall.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Bytes, ethereum, log, crypto, ByteArray, Address } from "@graphprotocol/graph-ts" -import { Multicall3 as Multicall3Contract } from "../../generated/templates/BeefyVaultV7/Multicall3" - -const MULTICALL3_ADDRESS = Address.fromString("0xcA11bde05977b3631167028862bE2a173976CA11") - -export class Multicall3Params { - constructor( - public contractAddress: Bytes, - public functionSignature: string, - public resultType: string, - public allowFailure: boolean = false, - ) {} -} - -class MulticallResult { - constructor( - public value: ethereum.Value, - public reverted: boolean, - ) {} -} - -export function multicall(callParams: Array): Array { - const multicallContract = Multicall3Contract.bind(MULTICALL3_ADDRESS) - - let params: Array = [] - for (let i = 0; i < callParams.length; i++) { - const callParam = callParams[i] - const sig = Bytes.fromUint8Array(crypto.keccak256(ByteArray.fromUTF8(callParam.functionSignature)).slice(0, 4)) - params.push( - // @ts-ignore - changetype([ - ethereum.Value.fromAddress(Address.fromBytes(callParam.contractAddress)), - ethereum.Value.fromBoolean(callParam.allowFailure), - ethereum.Value.fromBytes(sig), - ]), - ) - } - - // need a low level call, can't call aggregate due to typing issues - const callResult = multicallContract.tryCall("aggregate3", "aggregate3((address,bool,bytes)[]):((bool,bytes)[])", [ - ethereum.Value.fromTupleArray(params), - ]) - if (callResult.reverted) { - log.error("Multicall failed", []) - throw Error("Multicall failed") - } - - const multiResults: Array = callResult.value[0].toTupleArray() - let results: Array = [] - for (let i = 0; i < callParams.length; i++) { - const callParam = callParams[i] - const res = multiResults[i] - const success = res[0].toBoolean() - if (!success && !callParam.allowFailure) { - log.error("Multicall failed for {}", [callParam.functionSignature]) - throw Error("Multicall failed") - } - - if (success) { - results.push(new MulticallResult(ethereum.decode(callParam.resultType, res[1].toBytes())!, !success)) - } else { - results.push(new MulticallResult(ethereum.Value.fromBytes(Bytes.fromI32(0)), !success)) - } - } - - return results -} diff --git a/src/vault-interaction.ts b/src/vault-interaction.ts deleted file mode 100644 index d96472f..0000000 --- a/src/vault-interaction.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Address, log, BigInt } from "@graphprotocol/graph-ts" -import { Transfer as TransferEvent, BeefyVaultV7 as BeefyVaultV7Contract } from "../generated/templates/BeefyVaultV7/BeefyVaultV7" -import { getBeefyVault, isVaultInitialized } from "./entity/vault" -import { ZERO_BI, tokenAmountToDecimal } from "./utils/decimal" -import { getTokenAndInitIfNeeded } from "./entity/token" -import { SHARE_TOKEN_MINT_ADDRESS } from "./config" -import { getInvestor } from "./entity/investor" -import { getInvestorPosition } from "./entity/position" -import { BeefyVault, Investor } from "../generated/schema" -import { Multicall3Params, multicall } from "./utils/multicall" - -export function handleVaultTransfer(event: TransferEvent): void { - // transfer to self - if (event.params.from.equals(event.params.to)) { - log.warning("handleVaultTransfer: transfer to self, ignoring {}", [event.transaction.hash.toHexString()]) - return - } - - /// value is zero - if (event.params.value.equals(ZERO_BI)) { - log.warning("handleVaultTransfer: zero value transfer {}", [event.transaction.hash.toHexString()]) - return - } - - let vault = getBeefyVault(event.address) - if (!isVaultInitialized(vault)) { - log.warning("handleVaultTransfer: vault is not initialized {}", [vault.id.toHexString()]) - return - } - const sharesToken = getTokenAndInitIfNeeded(vault.sharesToken) - - if (event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS)) { - // minting - vault.rawSharesTokenTotalSupply = vault.rawSharesTokenTotalSupply.plus(event.params.value) - vault.sharesTokenTotalSupply = tokenAmountToDecimal(vault.rawSharesTokenTotalSupply, sharesToken.decimals) - } else { - const investorAddress = event.params.from - const investor = getInvestor(investorAddress) - investor.save() - updateInvestorVaultData(vault, investor, event.params.value.neg()) - } - - if (event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS)) { - // burning - vault.rawSharesTokenTotalSupply = vault.rawSharesTokenTotalSupply.minus(event.params.value) - vault.sharesTokenTotalSupply = tokenAmountToDecimal(vault.rawSharesTokenTotalSupply, sharesToken.decimals) - } else { - const investorAddress = event.params.to - const investor = getInvestor(investorAddress) - investor.save() - updateInvestorVaultData(vault, investor, event.params.value) - } -} - -function updateInvestorVaultData(vault: BeefyVault, investor: Investor, sharesDiff: BigInt): Investor { - const sharesToken = getTokenAndInitIfNeeded(vault.sharesToken) - - /////// - // update investor positions - const position = getInvestorPosition(vault, investor) - position.rawSharesBalance = position.rawSharesBalance.plus(sharesDiff) - position.sharesBalance = tokenAmountToDecimal(position.rawSharesBalance, sharesToken.decimals) - position.save() - - return investor -} diff --git a/src/vault-lifecycle.ts b/src/vault-lifecycle.ts deleted file mode 100644 index f2c73b9..0000000 --- a/src/vault-lifecycle.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Address, ethereum, store } from "@graphprotocol/graph-ts" -import { BeefyVaultV7 as BeefyVaultV7Contract, UpgradeStrat } from "../generated/templates/BeefyVaultV7/BeefyVaultV7" -import { BEEFY_VAULT_LIFECYCLE_RUNNING, getBeefyStrategy, getBeefyVault } from "./entity/vault" -import { BeefyIStrategyV7 as BeefyIStrategyV7Template, BeefyVaultV7 as BeefyVaultV7Template } from "../generated/templates" -import { ADDRESS_ZERO } from "./utils/address" -import { BeefyIStrategyV7 as BeefyIStrategyV7Contract } from "../generated/templates/BeefyIStrategyV7/BeefyIStrategyV7" -import { BeefyVault } from "../generated/schema" -import { getTokenAndInitIfNeeded } from "./entity/token" -import { ProxyCreated as VaultCreated } from "../generated/BeefyVaultV7Factory/BeefyVaultV7Factory" -import { log } from "matchstick-as" - -export function handleVaultCreated(event: VaultCreated): void { - // start watching the vault events - const vaultAddress = event.params.proxy - log.debug("Vault created: {}", [vaultAddress.toHexString()]) - - // test if we are creating a vault or a strategy - const vaultContract = BeefyVaultV7Contract.bind(vaultAddress) - const strategyRes = vaultContract.try_strategy() - // proxy also creates the strategies - if (strategyRes.reverted) { - log.warning("`strategy()` method does not exist on contract: {}. It's not a vault", [vaultAddress.toHexString()]) - return - } - - const vault = getBeefyVault(vaultAddress) - vault.save() - - BeefyVaultV7Template.create(vaultAddress) -} - -export function handleVaultInitialized(event: ethereum.Event): void { - const vaultAddress = event.address - log.debug("Vault initialized: {}", [vaultAddress.toHexString()]) - - let vault = getBeefyVault(vaultAddress) - // some chains don't have a proper initialized event so - // we hook into another event that may trigger multiple times - if (vault.isInitialized) { - return - } - - const vaultContract = BeefyVaultV7Contract.bind(vaultAddress) - const strategyAddress = vaultContract.strategy() - - vault.isInitialized = true - vault.strategy = strategyAddress - vault.initializedAtBlockNumber = event.block.number - vault.initializedAtTimestamp = event.block.timestamp - vault.save() // needs to be saved before we can use it in the strategy events - - // we start watching strategy events - BeefyIStrategyV7Template.create(strategyAddress) - - const strategy = getBeefyStrategy(strategyAddress) - // the strategy may or may not be initialized - // this is a test to know if that is the case - const strategyContract = BeefyIStrategyV7Contract.bind(strategyAddress) - const strategyVaultRes = strategyContract.try_vault() - if (strategyVaultRes.reverted) { - log.warning("strategy not yet initialized or is not a proper strategy: {}", [strategyAddress.toHexString()]) - return - } - const strategyVault = strategyVaultRes.value - strategy.isInitialized = !strategyVault.equals(ADDRESS_ZERO) - - if (strategy.isInitialized) { - vault = fetchInitialVaultData(vault) - vault.save() - } -} - -export function handleStrategyInitialized(event: ethereum.Event): void { - const strategyAddress = event.address - log.debug("Strategy initialized: {}", [strategyAddress.toHexString()]) - - const strategyContract = BeefyIStrategyV7Contract.bind(strategyAddress) - const vaultAddressRes = strategyContract.try_vault() - if (vaultAddressRes.reverted) { - log.warning("is not a proper strategy: {}", [strategyAddress.toHexString()]) - return - } - const vaultAddress = vaultAddressRes.value - - const strategy = getBeefyStrategy(strategyAddress) - strategy.isInitialized = true - strategy.vault = vaultAddress - strategy.save() - - let vault = getBeefyVault(vaultAddress) - if (vault.isInitialized) { - vault = fetchInitialVaultData(vault) - vault.save() - } -} - -/** - * Initialize the vault data. - * Call this when both the vault and the strategy are initialized. - */ -function fetchInitialVaultData(vault: BeefyVault): BeefyVault { - const vaultAddress = Address.fromBytes(vault.id) - const vaultContract = BeefyVaultV7Contract.bind(vaultAddress) - - const underlyingTokenAddress = vaultContract.want() - - const sharesToken = getTokenAndInitIfNeeded(vaultAddress) - const underlyingToken = getTokenAndInitIfNeeded(underlyingTokenAddress) - - vault.sharesToken = sharesToken.id - vault.underlyingToken = underlyingToken.id - vault.lifecycle = BEEFY_VAULT_LIFECYCLE_RUNNING - - return vault -} - -export function handleUpgradeStrat(event: UpgradeStrat): void { - const vault = getBeefyVault(event.address) - const newStrategyAddress = event.params.implementation - const oldStrategyAddress = vault.strategy - vault.strategy = newStrategyAddress - vault.save() - - // we start watching the new strategy events - BeefyIStrategyV7Template.create(newStrategyAddress) - - // create the new strategy entity - const newStrategy = getBeefyStrategy(newStrategyAddress) - newStrategy.isInitialized = true - newStrategy.vault = vault.id - newStrategy.save() - - // make sure we deprecated the old strategy - // so events are ignored - const oldStrategy = getBeefyStrategy(oldStrategyAddress) - oldStrategy.isInitialized = false - oldStrategy.vault = ADDRESS_ZERO - oldStrategy.save() -} diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 64ee08d..4a33510 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -21,34 +21,22 @@ dataSources: language: wasm/assemblyscript file: ./src/clm/mapping/manager-factory.ts entities: &clmEntities - - CLM - - ClmPosition - - ClmManager - - ClmRewardPool - - Investor - Token - - Transaction + - Account + - TokenBalance abis: &abis - name: ClmManagerFactory file: ./abis/beefy/clm/ClmManagerFactory.json - - name: ClmManager - file: ./abis/beefy/clm/ClmManager.json - name: RewardPoolFactory file: ./abis/beefy/clm/RewardPoolFactory.json - - name: RewardPool - file: ./abis/beefy/clm/RewardPool.json - - name: ClassicVaultFactory file: ./abis/beefy/classic/ClassicVaultFactory.json - name: ClassicVault file: ./abis/beefy/classic/ClassicVault.json - name: ClassicBoostFactory file: ./abis/beefy/classic/ClassicBoostFactory.json - - name: ClassicBoost - file: ./abis/beefy/classic/ClassicBoost.json - - - name: Multicall3 - file: ./abis/multicall/Multicall3.json + - name: IERC20 + file: ./abis/IERC20/IERC20.json eventHandlers: - event: ProxyCreated(address) handler: handleClmManagerCreated @@ -107,59 +95,28 @@ dataSources: kind: ethereum/events apiVersion: 0.0.7 # 0xgraph's version language: wasm/assemblyscript - file: ./src/classic/mapping/boot-factory.ts + file: ./src/classic/mapping/boost-factory.ts entities: *clmEntities abis: *abis eventHandlers: - - event: BoostDeployed(address) + - event: BoostDeployed(indexed address) handler: handleClassicBoostCreated templates: - - name: ClmManager - kind: ethereum/contract - network: {{network}} - source: - abi: ClmManager - mapping: - kind: ethereum/events - apiVersion: 0.0.7 # 0xgraph's version - language: wasm/assemblyscript - file: ./src/clm/mapping/manager.ts - entities: *clmEntities - abis: *abis - eventHandlers: - - event: Transfer(indexed address,indexed address,uint256) - handler: handleClmManagerTransfer - - - name: ClmRewardPool + - name: BeefyERC20Product kind: ethereum/contract network: {{network}} source: - abi: RewardPool + abi: IERC20 mapping: kind: ethereum/events apiVersion: 0.0.7 # 0xgraph's version language: wasm/assemblyscript - file: ./src/clm/mapping/reward-pool.ts + file: ./src/common/mapping/erc20.ts entities: *clmEntities abis: *abis eventHandlers: - event: Transfer(indexed address,indexed address,uint256) - handler: handleRewardPoolTransfer - - - name: ClassicVault - kind: ethereum/contract - network: {{network}} - source: - abi: ClassicVault - mapping: - kind: ethereum/events - apiVersion: 0.0.7 # 0xgraph's version - language: wasm/assemblyscript - file: ./src/classic/mapping/vault.ts - entities: *classicEntities - abis: *abis - eventHandlers: - - event: Transfer(indexed address,indexed address,uint256) - handler: handleClassicVaultTransfer + handler: handleProductTransfer + \ No newline at end of file diff --git a/tests/utils/decimals.test.ts b/tests/utils/decimals.test.ts deleted file mode 100644 index dff0569..0000000 --- a/tests/utils/decimals.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { assert, clearStore, test, describe, afterAll } from "matchstick-as/assembly/index" -import { BigDecimal, BigInt, log } from "@graphprotocol/graph-ts" -import { bigDecMax, bigIntMax, bigDecMin, bigIntMin, decimalToTokenAmount, exponentToBigDecimal, tokenAmountToDecimal } from "../../src/utils/decimal" - -describe("decimals.tokenAmountToDecimal", () => { - afterAll(() => { - clearStore() - }) - - test("Can transform a simple value into a decimal value", () => { - const value = BigInt.fromString("1000000000000000000") - const res = tokenAmountToDecimal(value, BigInt.fromI32(18)) - assert.stringEquals(res.toString(), "1", "Decimal value should match") - }) - - test("Can transform a simple value into a decimal value with 6 decimals", () => { - const value = BigInt.fromString("1000000") - const res = tokenAmountToDecimal(value, BigInt.fromI32(6)) - assert.stringEquals(res.toString(), "1", "Decimal value should match") - }) - - test("Can transform a simple value into a decimal value with 0 decimals", () => { - const value = BigInt.fromString("1000000") - const res = tokenAmountToDecimal(value, BigInt.fromI32(0)) - assert.stringEquals(res.toString(), "1000000", "Decimal value should match") - }) - - test("Can transform a value with many decimals value into a decimal object as long as the upper digit count is less than 34", () => { - const value = BigInt.fromString("123456789012345678901234567890") - const res = tokenAmountToDecimal(value, BigInt.fromI32(18)) - assert.stringEquals(res.toString(), "123456789012.34567890123456789", "Decimal value should match") - }) -}) - -describe("decimals.decimalToTokenAmount", () => { - afterAll(() => { - clearStore() - }) - - test("Can transform a simple decimal value into a token amount", () => { - const value = BigDecimal.fromString("1") - const res = decimalToTokenAmount(value, BigInt.fromI32(18)) - assert.stringEquals(res.toString(), "1000000000000000000", "Token amount should match") - }) - - test("Can transform a simple decimal value with 6 decimals into a token amount", () => { - const value = BigDecimal.fromString("1") - const res = decimalToTokenAmount(value, BigInt.fromI32(6)) - assert.stringEquals(res.toString(), "1000000", "Token amount should match") - }) - - test("Can transform a simple decimal value with 0 decimals into a token amount", () => { - const value = BigDecimal.fromString("1000000") - const res = decimalToTokenAmount(value, BigInt.fromI32(0)) - assert.stringEquals(res.toString(), "1000000", "Token amount should match") - }) - - test("Can transform a decimal value with many decimals into a token amount", () => { - const value = BigDecimal.fromString("123456789012.34567890123456789") - const res = decimalToTokenAmount(value, BigInt.fromI32(18)) - assert.stringEquals(res.toString(), "123456789012345678901234567890", "Token amount should match") - }) -}) - -describe("decimals.exponentToBigDecimal", () => { - afterAll(() => { - clearStore() - }) - - test("Can transform a simple value into a decimal value", () => { - const value = BigInt.fromI32(18) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1000000000000000000", "Decimal value should match") - }) - - test("Can transform a simple value into a decimal value with 6 decimals", () => { - const value = BigInt.fromI32(6) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1000000", "Decimal value should match") - }) - - test("Can transform a simple value into a decimal value with 0 decimals", () => { - const value = BigInt.fromI32(0) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1", "Decimal value should match") - }) -}) - -describe("decimals.exponentToBigInt", () => { - afterAll(() => { - clearStore() - }) - - test("Can transform a simple value into a big int value", () => { - const value = BigInt.fromI32(18) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1000000000000000000", "Decimal value should match") - }) - - test("Can transform a simple value into a big int value with 6 decimals", () => { - const value = BigInt.fromI32(6) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1000000", "Decimal value should match") - }) - - test("Can transform a simple value into a big int value with 0 decimals", () => { - const value = BigInt.fromI32(0) - const res = exponentToBigDecimal(value) - assert.stringEquals(res.toString(), "1", "Decimal value should match") - }) -}) - -describe("decimals min max", () => { - test("Can return the minimum of two big decimals", () => { - const a = BigDecimal.fromString("1") - const b = BigDecimal.fromString("2") - const res = bigDecMin(a, b) - assert.stringEquals(res.toString(), "1", "Minimum value should match") - }) - test("Can return the max of two big decimals", () => { - const a = BigDecimal.fromString("1") - const b = BigDecimal.fromString("2") - const res = bigDecMax(a, b) - assert.stringEquals(res.toString(), "2", "Minimum value should match") - }) - - test("Can return the minimum of two big ints", () => { - const a = BigInt.fromI32(1) - const b = BigInt.fromI32(2) - const res = bigIntMin(a, b) - assert.stringEquals(res.toString(), "1", "Minimum value should match") - }) - test("Can return the max of two big ints", () => { - const a = BigInt.fromI32(1) - const b = BigInt.fromI32(2) - const res = bigIntMax(a, b) - assert.stringEquals(res.toString(), "2", "Minimum value should match") - }) -})