From 2cf1c2ddc3f4c4ae104f82ec4bca1a5f0f0ae486 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Thu, 2 Jan 2025 23:54:11 +0200 Subject: [PATCH 01/17] Batch calls into multicall --- package-lock.json | 54 ++--- package.json | 2 +- src/abis/StrategiesRegistry.json | 378 +++++++++++++++++++++++++++++ src/config/chiado.json | 4 + src/config/gnosis.json | 4 + src/config/holesky.json | 4 + src/config/mainnet.json | 4 + src/entities/aave.ts | 34 ++- src/entities/allocator.ts | 28 ++- src/entities/exchangeRates.ts | 132 ++++++++++ src/entities/exitRequest.ts | 89 ++++--- src/entities/leverageStrategy.ts | 24 +- src/entities/osTokenConfig.ts | 1 + src/entities/osTokenVaultEscrow.ts | 59 ++++- src/entities/rewardSplitter.ts | 20 +- src/entities/v2pool.ts | 48 ++-- src/entities/vault.ts | 92 +++---- src/helpers/utils.ts | 87 ++++++- src/mappings/exchangeRates.ts | 159 +----------- src/mappings/keeper.ts | 12 +- src/mappings/leverageStrategy.ts | 20 +- src/mappings/osTokenConfig.ts | 6 +- src/mappings/periodicTasks.ts | 11 +- src/mappings/strategiesRegistry.ts | 35 +++ src/schema.graphql | 12 +- src/subgraph.template.yaml | 27 +++ 26 files changed, 990 insertions(+), 356 deletions(-) create mode 100644 src/abis/StrategiesRegistry.json create mode 100644 src/entities/exchangeRates.ts create mode 100644 src/mappings/strategiesRegistry.ts diff --git a/package-lock.json b/package-lock.json index 10871d0..ed29a97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "@eslint/eslintrc": "3.2.0", "@eslint/js": "9.17.0", - "@graphprotocol/graph-cli": "0.93.0", + "@graphprotocol/graph-cli": "0.93.3", "@graphprotocol/graph-ts": "0.37.0", "@types/node": "22.10.2", "@typescript-eslint/eslint-plugin": "8.18.1", @@ -217,9 +217,9 @@ } }, "node_modules/@graphprotocol/graph-cli": { - "version": "0.93.0", - "resolved": "https://registry.npmjs.org/@graphprotocol/graph-cli/-/graph-cli-0.93.0.tgz", - "integrity": "sha512-oA49GWeUv3eIO9DE9PSLR7vEWkXwfM/MAir7P2Y9teQxTgXDjB/x8UPY87DsN9PEfETEIOU1T+3ezkL/cupW4g==", + "version": "0.93.3", + "resolved": "https://registry.npmjs.org/@graphprotocol/graph-cli/-/graph-cli-0.93.3.tgz", + "integrity": "sha512-ofxD3hmpuIM1irWEvVHHoyU7miy4So51Zh2kWY5jwlkt6GmyAcKRnNHZKSW4GI+/d/dgBk32yDy1aqGHzYjiQQ==", "dev": true, "license": "(Apache-2.0 OR MIT)", "dependencies": { @@ -255,7 +255,7 @@ "graph": "bin/run.js" }, "engines": { - "node": ">=18" + "node": ">=20.18.1" } }, "node_modules/@graphprotocol/graph-cli/node_modules/debug": { @@ -1049,14 +1049,14 @@ } }, "node_modules/@oclif/plugin-autocomplete": { - "version": "3.2.14", - "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.14.tgz", - "integrity": "sha512-JmUZ3KsPskoEYDNjGc/UG+U+cz0hVZ/VHhPM1Y814Jx3X2uUHYlb82UYS7DNRoDEHom2GAfd5fhsli8qA+K6XA==", + "version": "3.2.16", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-3.2.16.tgz", + "integrity": "sha512-KnfsBGUkwk9LFzeV1M2i4Sd7WPXbx2gnjHy/+h2DPQGXVQ4xg9puSIYCyXyLKXUujb+Ps5+6DNvyNdDJ/8Tytw==", "dev": true, "license": "MIT", "dependencies": { "@oclif/core": "^4", - "ansis": "^3.3.1", + "ansis": "^3.5.2", "debug": "^4.4.0", "ejs": "^3.1.10" }, @@ -1065,15 +1065,15 @@ } }, "node_modules/@oclif/plugin-not-found": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.31.tgz", - "integrity": "sha512-EF3GmrenxqPLVCU75xLWbwohH1ot/yg+31vyPa4qJAkFspnmQ/Jv28afyk6q4ePcTA5lRC9PYqla5QsALngt3A==", + "version": "3.2.33", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.33.tgz", + "integrity": "sha512-1RgvZ0J5KloU8TRemHxCr5MbVtr41ungnz8BBCPJn2yR5L+Eo2Lt+kpOyEeYAohjo4Tml1AHSmipUF4jKThwTw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/prompts": "^7.2.0", + "@inquirer/prompts": "^7.2.1", "@oclif/core": "^4", - "ansis": "^3.3.1", + "ansis": "^3.5.2", "fast-levenshtein": "^3.0.0" }, "engines": { @@ -1081,14 +1081,14 @@ } }, "node_modules/@oclif/plugin-warn-if-update-available": { - "version": "3.1.28", - "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-3.1.28.tgz", - "integrity": "sha512-15UI0yrzsodQLltntbUOmR7Nyd1tmUWzbcnR7QJvWMgEMuImNjqNdSaeXUFc3UBtaK266YatJYJWL5SCb/MTWw==", + "version": "3.1.29", + "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-3.1.29.tgz", + "integrity": "sha512-NmD6hcyBquo9TV26tnYnsbyR69VzaeMC3kqks0YT2947CSJS7smONMxpkjU2P4kLTH4Tn8/n5w/sjoegNe1jdw==", "dev": true, "license": "MIT", "dependencies": { "@oclif/core": "^4", - "ansis": "^3.4.0", + "ansis": "^3.5.2", "debug": "^4.4.0", "http-call": "^5.2.2", "lodash": "^4.17.21", @@ -1668,9 +1668,9 @@ } }, "node_modules/ansis": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.4.0.tgz", - "integrity": "sha512-zVESKSQhWaPhGaWiKj1k+UqvpC7vPBBgG3hjQEeIx2YGzylWt8qA3ziAzRuUtm0OnaGsZKjIvfl8D/sJTt/I0w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.5.2.tgz", + "integrity": "sha512-5uGcUZRbORJeEppVdWfZOSybTMz+Ou+84HepgK081Yk5+pPMMzWf/XGxiAT6bfBqCghRB4MwBtYn0CHqINRVag==", "dev": true, "license": "ISC", "engines": { @@ -3106,9 +3106,9 @@ } }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "license": "ISC", "dependencies": { @@ -5814,9 +5814,9 @@ } }, "node_modules/p-timeout": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.3.tgz", - "integrity": "sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 969b5f6..866bc92 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "devDependencies": { "@eslint/eslintrc": "3.2.0", "@eslint/js": "9.17.0", - "@graphprotocol/graph-cli": "0.93.0", + "@graphprotocol/graph-cli": "0.93.3", "@graphprotocol/graph-ts": "0.37.0", "@types/node": "22.10.2", "@typescript-eslint/eslint-plugin": "8.18.1", diff --git a/src/abis/StrategiesRegistry.json b/src/abis/StrategiesRegistry.json new file mode 100644 index 0000000..9e054a8 --- /dev/null +++ b/src/abis/StrategiesRegistry.json @@ -0,0 +1,378 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessDenied", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStrategyId", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidStrategyProxyId", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ValueNotChanged", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "configName", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "name": "StrategyConfigUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "StrategyProxyAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "StrategyUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "addStrategyProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "configName", + "type": "string" + } + ], + "name": "getStrategyConfig", + "outputs": [ + { + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "configName", + "type": "string" + }, + { + "internalType": "bytes", + "name": "value", + "type": "bytes" + } + ], + "name": "setStrategyConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategy", + "type": "address" + } + ], + "name": "strategies", + "outputs": [ + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "strategyProxies", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "strategyProxyId", + "type": "bytes32" + } + ], + "name": "strategyProxyIdToProxy", + "outputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/config/chiado.json b/src/config/chiado.json index e4591fc..9df61e1 100644 --- a/src/config/chiado.json +++ b/src/config/chiado.json @@ -105,5 +105,9 @@ "uniswapPositionManager": { "address": "0x0000000000000000000000000000000000000000", "startBlock": "10626869" + }, + "strategiesRegistry": { + "address": "0x4abB9BBb82922A6893A5d6890cd2eE94610BEc48", + "startBlock": "36651385" } } diff --git a/src/config/gnosis.json b/src/config/gnosis.json index a671c85..3822495 100644 --- a/src/config/gnosis.json +++ b/src/config/gnosis.json @@ -105,5 +105,9 @@ "uniswapPositionManager": { "address": "0x0000000000000000000000000000000000000000", "startBlock": "34778574" + }, + "strategiesRegistry": { + "address": "0x4abB9BBb82922A6893A5d6890cd2eE94610BEc48", + "startBlock": "12438574" } } diff --git a/src/config/holesky.json b/src/config/holesky.json index 0bcaac8..7f4a4ab 100644 --- a/src/config/holesky.json +++ b/src/config/holesky.json @@ -105,5 +105,9 @@ "uniswapPositionManager": { "address": "0x0000000000000000000000000000000000000000", "startBlock": "2673700" + }, + "strategiesRegistry": { + "address": "0xFc8E3E7c919b4392D9F5B27015688e49c80015f0", + "startBlock": "2579173" } } diff --git a/src/config/mainnet.json b/src/config/mainnet.json index a2ee202..63f51e4 100644 --- a/src/config/mainnet.json +++ b/src/config/mainnet.json @@ -105,5 +105,9 @@ "uniswapPositionManager": { "address": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", "startBlock": "18276054" + }, + "strategiesRegistry": { + "address": "0x90b82E4b3aa385B4A02B7EBc1892a4BeD6B5c465", + "startBlock": "21024037" } } diff --git a/src/entities/aave.ts b/src/entities/aave.ts index 3d38054..7e26535 100644 --- a/src/entities/aave.ts +++ b/src/entities/aave.ts @@ -1,4 +1,4 @@ -import { Address, BigDecimal, BigInt, log } from '@graphprotocol/graph-ts' +import { Address, BigDecimal, BigInt, Bytes, ethereum, log } from '@graphprotocol/graph-ts' import { Aave, AavePosition, OsToken } from '../../generated/schema' import { AaveProtocolDataProvider as AaveProtocolDataProviderContract } from '../../generated/PeriodicTasks/AaveProtocolDataProvider' import { AaveLeverageStrategy } from '../../generated/PeriodicTasks/AaveLeverageStrategy' @@ -10,12 +10,13 @@ import { OS_TOKEN, WAD, } from '../helpers/constants' -import { calculateMedian, getCompoundedApy } from '../helpers/utils' +import { calculateMedian, chunkedMulticall, getCompoundedApy } from '../helpers/utils' import { getOsTokenApy } from './osToken' const aaveId = '1' const snapshotsPerWeek = 168 const snapshotsPerDay = 24 +const getBorrowStateSelector = '0xe70631bc' export function loadAave(): Aave | null { return Aave.load(aaveId) @@ -31,6 +32,7 @@ export function createOrLoadAave(): Aave { aave = new Aave(aaveId) aave.borrowApy = BigDecimal.zero() aave.supplyApy = BigDecimal.zero() + aave.leverageMaxBorrowLtvPercent = BigInt.zero() aave.borrowApys = [] aave.supplyApys = [] aave.save() @@ -101,6 +103,29 @@ export function updateAaveApys(aave: Aave, blockNumber: BigInt): void { aave.save() } +export function updateAavePositions(aave: Aave): void { + const positions: Array = aave.positions.load() + const positionsCount = positions.length + + let position: AavePosition + const contractAddresses: Array
= [] + const contractCalls: Array = [] + for (let i = 0; i < positionsCount; i++) { + position = positions[i] + contractAddresses.push(AAVE_LEVERAGE_STRATEGY) + contractCalls.push(_getBorrowStateCall(Address.fromBytes(position.user))) + } + + const result = chunkedMulticall(contractAddresses, contractCalls) + for (let i = 0; i < positionsCount; i++) { + position = positions[i] + let decodedResult = ethereum.decode('(uint256,uint256)', result[i]!)!.toTuple() + position.borrowedAssets = decodedResult[0].toBigInt() + position.suppliedOsTokenShares = decodedResult[1].toBigInt() + position.save() + } +} + export function updateAavePosition(position: AavePosition): void { const aaveLeverageStrategy = AaveLeverageStrategy.bind(AAVE_LEVERAGE_STRATEGY) const borrowState = aaveLeverageStrategy.getBorrowState(Address.fromBytes(position.user)) @@ -132,3 +157,8 @@ export function getAaveBorrowApy(aave: Aave, useDayApy: boolean): BigDecimal { const apys: Array = aave.borrowApys return calculateMedian(apys.slice(apysCount - snapshotsPerDay)) } + +function _getBorrowStateCall(user: Address): Bytes { + const encodedGetBorrowStateArgs = ethereum.encode(ethereum.Value.fromAddress(user)) + return Bytes.fromHexString(getBorrowStateSelector).concat(encodedGetBorrowStateArgs!) +} diff --git a/src/entities/allocator.ts b/src/entities/allocator.ts index 20eb0c4..0897616 100644 --- a/src/entities/allocator.ts +++ b/src/entities/allocator.ts @@ -10,12 +10,11 @@ import { Vault, } from '../../generated/schema' import { WAD } from '../helpers/constants' -import { getAnnualReward } from '../helpers/utils' +import { chunkedVaultMulticall, getAnnualReward } from '../helpers/utils' import { convertOsTokenSharesToAssets, getOsTokenApy } from './osToken' import { convertSharesToAssets, getVaultApy, getVaultOsTokenMintApy, loadVault } from './vault' import { loadOsTokenConfig } from './osTokenConfig' import { getBoostPositionAnnualReward, loadLeverageStrategyPosition } from './leverageStrategy' -import { Vault as VaultContract } from '../../generated/PeriodicTasks/Vault' import { loadNetwork } from './network' import { loadAave } from './aave' @@ -117,26 +116,29 @@ export function createAllocatorAction( } export function getAllocatorsMintedShares(vault: Vault, allocators: Array): Array { + const allocatorsCount = allocators.length if (!vault.isOsTokenEnabled) { - let response = new Array(allocators.length) - for (let i = 0; i < allocators.length; i++) { + // If OsToken is disabled, just return zeros + let response = new Array(allocatorsCount) + for (let i = 0; i < allocatorsCount; i++) { response[i] = BigInt.zero() } return response } - const vaultAddress = Address.fromString(vault.id) - const vaultContract = VaultContract.bind(vaultAddress) - - let calls: Array = [] - for (let i = 0; i < allocators.length; i++) { + // Prepare all calls for retrieving minted shares from OsToken positions + let calls = new Array() + for (let i = 0; i < allocatorsCount; i++) { calls.push(_getOsTokenPositionsCall(allocators[i])) } - const result = vaultContract.multicall(calls) - const mintedShares: Array = [] - for (let i = 0; i < allocators.length; i++) { - mintedShares.push(ethereum.decode('uint256', result[i])!.toBigInt()) + // Execute calls in chunks of size 10 + let results = chunkedVaultMulticall(Address.fromString(vault.id), calls) + + // Decode the result for each allocator in the same order + let mintedShares = new Array() + for (let i = 0; i < allocatorsCount; i++) { + mintedShares.push(ethereum.decode('uint256', results[i])!.toBigInt()) } return mintedShares } diff --git a/src/entities/exchangeRates.ts b/src/entities/exchangeRates.ts new file mode 100644 index 0000000..a165dfa --- /dev/null +++ b/src/entities/exchangeRates.ts @@ -0,0 +1,132 @@ +import { ExchangeRateSnapshot, Network, UniswapPool } from '../../generated/schema' +import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' +import { + ASSETS_USD_PRICE_FEED, + AUD_USD_PRICE_FEED, + CNY_USD_PRICE_FEED, + DAI_USD_PRICE_FEED, + EUR_USD_PRICE_FEED, + GBP_USD_PRICE_FEED, + JPY_USD_PRICE_FEED, + KRW_USD_PRICE_FEED, + SWISE_ASSET_UNI_POOL, + USDC_USD_PRICE_FEED, +} from '../helpers/constants' +import { chunkedMulticall } from '../helpers/utils' + +const latestAnswerSelector = '0x50d25bcd' + +export function updateExchangeRates(network: Network, timestamp: BigInt): void { + const latestAnswerCall = Bytes.fromHexString(latestAnswerSelector) + const decimals = BigDecimal.fromString('100000000') + + let assetsUsdRate = BigDecimal.zero() + let eurToUsdRate = BigDecimal.zero() + let gbpToUsdRate = BigDecimal.zero() + let cnyToUsdRate = BigDecimal.zero() + let jpyToUsdRate = BigDecimal.zero() + let krwToUsdRate = BigDecimal.zero() + let audToUsdRate = BigDecimal.zero() + let daiUsdRate = BigDecimal.zero() + let usdcUsdRate = BigDecimal.zero() + let swiseUsdRate = BigDecimal.zero() + + const contractAddresses: Array
= [ + Address.fromString(ASSETS_USD_PRICE_FEED), + Address.fromString(EUR_USD_PRICE_FEED), + Address.fromString(GBP_USD_PRICE_FEED), + Address.fromString(CNY_USD_PRICE_FEED), + Address.fromString(JPY_USD_PRICE_FEED), + Address.fromString(KRW_USD_PRICE_FEED), + Address.fromString(AUD_USD_PRICE_FEED), + Address.fromString(DAI_USD_PRICE_FEED), + Address.fromString(USDC_USD_PRICE_FEED), + ] + const contractCalls: Array = [] + for (let i = 0; i < contractAddresses.length; i++) { + contractCalls.push(latestAnswerCall) + } + + let decodedValue: BigInt = BigInt.zero() + const response = chunkedMulticall(contractAddresses, contractCalls, false) + if (response[0] !== null) { + decodedValue = ethereum.decode('int256', response[0]!)!.toBigInt() + assetsUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[1] !== null) { + decodedValue = ethereum.decode('int256', response[1]!)!.toBigInt() + eurToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[2] !== null) { + decodedValue = ethereum.decode('int256', response[2]!)!.toBigInt() + gbpToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[3] !== null) { + decodedValue = ethereum.decode('int256', response[3]!)!.toBigInt() + cnyToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[4] !== null) { + decodedValue = ethereum.decode('int256', response[4]!)!.toBigInt() + jpyToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[5] !== null) { + decodedValue = ethereum.decode('int256', response[5]!)!.toBigInt() + krwToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[6] !== null) { + decodedValue = ethereum.decode('int256', response[6]!)!.toBigInt() + audToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[7] !== null) { + decodedValue = ethereum.decode('int256', response[7]!)!.toBigInt() + daiUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[8] !== null) { + decodedValue = ethereum.decode('int256', response[8]!)!.toBigInt() + usdcUsdRate = decodedValue.toBigDecimal().div(decimals) + } + + const swiseAssetUniPool = Address.fromString(SWISE_ASSET_UNI_POOL) + if (swiseAssetUniPool.notEqual(Address.zero())) { + const pool = UniswapPool.load(swiseAssetUniPool.toHex()) + if (pool !== null) { + const swiseAssetRate = new BigDecimal(pool.sqrtPrice.pow(2)).div(new BigDecimal(BigInt.fromI32(2).pow(192))) + swiseUsdRate = swiseAssetRate.times(assetsUsdRate) + } + } + + const zero = BigDecimal.zero() + const one = BigDecimal.fromString('1') + const usdToEurRate = eurToUsdRate.gt(zero) ? one.div(eurToUsdRate) : zero + const usdToGbpRate = gbpToUsdRate.gt(zero) ? one.div(gbpToUsdRate) : zero + const usdToCnyRate = cnyToUsdRate.gt(zero) ? one.div(cnyToUsdRate) : zero + const usdToJpyRate = jpyToUsdRate.gt(zero) ? one.div(jpyToUsdRate) : zero + const usdToKrwRate = krwToUsdRate.gt(zero) ? one.div(krwToUsdRate) : zero + const usdToAudRate = audToUsdRate.gt(zero) ? one.div(audToUsdRate) : zero + + network.assetsUsdRate = assetsUsdRate + network.swiseUsdRate = swiseUsdRate + network.usdToEurRate = usdToEurRate + network.usdToGbpRate = usdToGbpRate + network.usdToCnyRate = usdToCnyRate + network.usdToJpyRate = usdToJpyRate + network.usdToKrwRate = usdToKrwRate + network.usdToAudRate = usdToAudRate + network.daiUsdRate = daiUsdRate + network.usdcUsdRate = usdcUsdRate + network.save() + + const exchangeRateSnapshot = new ExchangeRateSnapshot(timestamp.toString()) + exchangeRateSnapshot.timestamp = timestamp.toI64() + exchangeRateSnapshot.assetsUsdRate = assetsUsdRate + exchangeRateSnapshot.swiseUsdRate = swiseUsdRate + exchangeRateSnapshot.daiUsdRate = daiUsdRate + exchangeRateSnapshot.usdcUsdRate = usdcUsdRate + exchangeRateSnapshot.usdToEurRate = usdToEurRate + exchangeRateSnapshot.usdToGbpRate = usdToGbpRate + exchangeRateSnapshot.usdToCnyRate = usdToCnyRate + exchangeRateSnapshot.usdToJpyRate = usdToJpyRate + exchangeRateSnapshot.usdToKrwRate = usdToKrwRate + exchangeRateSnapshot.usdToAudRate = usdToAudRate + exchangeRateSnapshot.save() +} diff --git a/src/entities/exitRequest.ts b/src/entities/exitRequest.ts index 87937ba..fa99f08 100644 --- a/src/entities/exitRequest.ts +++ b/src/entities/exitRequest.ts @@ -1,10 +1,10 @@ import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { Distributor, ExitRequest, Network, OsToken, OsTokenConfig, Vault } from '../../generated/schema' -import { Vault as VaultContract } from '../../generated/Keeper/Vault' import { loadV2Pool } from './v2pool' import { convertSharesToAssets, getUpdateStateCall } from './vault' import { loadAllocator, snapshotAllocator } from './allocator' import { loadOsTokenHolder, snapshotOsTokenHolder } from './osTokenHolder' +import { chunkedVaultMulticall } from '../helpers/utils' const secondsInDay = '86400' const getExitQueueIndexSelector = '0x60d60e6e' @@ -23,42 +23,46 @@ export function updateExitRequests( osTokenConfig: OsTokenConfig, timestamp: BigInt, ): void { - if (vault.isGenesis) { - const v2Pool = loadV2Pool()! - if (!v2Pool.migrated) { - // wait for the migration - return - } + // If vault is in "genesis" mode, we need to wait for legacy migration + if (vault.isGenesis && !loadV2Pool()!.migrated) { + return } - const vaultAddress = Address.fromString(vault.id) - const vaultContract = VaultContract.bind(vaultAddress) const exitRequests: Array = vault.exitRequests.load() const updateStateCall: Bytes | null = getUpdateStateCall(vault) - let calls: Array = [] + // ───────────────────────────────────────────────────────────────────── + // STAGE 1: Query exitQueueIndex for all pending exit requests + // ───────────────────────────────────────────────────────────────────── + + // Collect all the calls for the first multicall batch + let allCallsStage1: Array = [] if (updateStateCall) { - calls.push(updateStateCall) + allCallsStage1.push(updateStateCall) } - let exitRequest: ExitRequest - const pendingExitRequests: Array = [] + + let pendingExitRequests: Array = [] for (let i = 0; i < exitRequests.length; i++) { - exitRequest = exitRequests[i] + let exitRequest = exitRequests[i] if (!exitRequest.isClaimed) { pendingExitRequests.push(exitRequest) - calls.push(getExitQueueIndexCall(exitRequest.positionTicket)) + allCallsStage1.push(getExitQueueIndexCall(exitRequest.positionTicket)) } } - let result = vaultContract.multicall(calls) + // Execute in chunks of size 10 + let stage1Results: Array = chunkedVaultMulticall(Address.fromString(vault.id), allCallsStage1) + + // If we had an updateStateCall, remove its result from the front + // so that the remainder of the results map cleanly to `pendingExitRequests`. if (updateStateCall) { - // remove first call result - result = result.slice(1) + stage1Results = stage1Results.slice(1) // remove first result } - for (let i = 0; i < result.length; i++) { - const index = ethereum.decode('int256', result[i])!.toBigInt() - exitRequest = pendingExitRequests[i] + // Parse exitQueueIndex results + for (let i = 0; i < stage1Results.length; i++) { + let exitRequest = pendingExitRequests[i] + let index = ethereum.decode('int256', stage1Results[i])!.toBigInt() if (index.lt(BigInt.zero())) { exitRequest.exitQueueIndex = null } else { @@ -66,15 +70,22 @@ export function updateExitRequests( } } - calls = [] + // ───────────────────────────────────────────────────────────────────── + // STAGE 2: Query exited assets for all pending exit requests + // ───────────────────────────────────────────────────────────────────── + + // Build calls for the second multicall batch + let allCallsStage2: Array = [] if (updateStateCall) { - calls.push(updateStateCall) + allCallsStage2.push(updateStateCall) } + const maxUint255 = BigInt.fromI32(2).pow(255).minus(BigInt.fromI32(1)) for (let i = 0; i < pendingExitRequests.length; i++) { - exitRequest = pendingExitRequests[i] - const exitQueueIndex = exitRequest.exitQueueIndex !== null ? exitRequest.exitQueueIndex! : maxUint255 - calls.push( + let exitRequest = pendingExitRequests[i] + let exitQueueIndex = exitRequest.exitQueueIndex !== null ? exitRequest.exitQueueIndex! : maxUint255 + + allCallsStage2.push( getCalculateExitedAssetsCall( Address.fromBytes(exitRequest.receiver), exitRequest.positionTicket, @@ -84,19 +95,25 @@ export function updateExitRequests( ) } - result = vaultContract.multicall(calls) + // Execute in chunks of size 10 + let stage2Results: Array = chunkedVaultMulticall(Address.fromString(vault.id), allCallsStage2) + + // If we had an updateStateCall, remove its result from the front again if (updateStateCall) { - // remove first call result - result = result.slice(1) + stage2Results = stage2Results.slice(1) } + // Parse and update each exitRequest const one = BigInt.fromI32(1) - for (let i = 0; i < result.length; i++) { - exitRequest = pendingExitRequests[i] - let decodedResult = ethereum.decode('(uint256,uint256,uint256)', result[i])!.toTuple() - const leftTickets = decodedResult[0].toBigInt() - const exitedAssets = decodedResult[2].toBigInt() - const totalAssetsBefore = exitRequest.totalAssets + for (let i = 0; i < stage2Results.length; i++) { + let exitRequest = pendingExitRequests[i] + let decodedResult = ethereum.decode('(uint256,uint256,uint256)', stage2Results[i])!.toTuple() + + let leftTickets = decodedResult[0].toBigInt() + let exitedAssets = decodedResult[2].toBigInt() + let totalAssetsBefore = exitRequest.totalAssets + + // If multiple tickets remain, recalculate total assets. Otherwise, set total to exitedAssets. if (leftTickets.gt(one)) { exitRequest.totalAssets = exitRequest.isV2Position ? leftTickets.times(vault.exitingAssets).div(vault.exitingTickets).plus(exitedAssets) @@ -106,11 +123,13 @@ export function updateExitRequests( } exitRequest.exitedAssets = exitedAssets + // If there are some exited assets, check if they are claimable if (!exitedAssets.isZero()) { exitRequest.isClaimable = exitRequest.timestamp.plus(BigInt.fromString(secondsInDay)).lt(timestamp) } else { exitRequest.isClaimable = false } + exitRequest.save() snapshotExitRequest( diff --git a/src/entities/leverageStrategy.ts b/src/entities/leverageStrategy.ts index 6204353..c1c94dd 100644 --- a/src/entities/leverageStrategy.ts +++ b/src/entities/leverageStrategy.ts @@ -1,6 +1,7 @@ import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts' import { Aave, + AavePosition, Distributor, ExitRequest, LeverageStrategyPosition, @@ -78,13 +79,20 @@ export function snapshotLeverageStrategyPosition( snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, totalAssetsDiff, timestamp) } -export function updateLeverageStrategyPosition(osToken: OsToken, position: LeverageStrategyPosition): void { - const aaveLeverageStrategy = AaveLeverageStrategy.bind(AAVE_LEVERAGE_STRATEGY) - +export function updateLeverageStrategyPosition(aave: Aave, osToken: OsToken, position: LeverageStrategyPosition): void { + if (aave.leverageMaxBorrowLtvPercent.isZero()) { + assert(false, 'Leverage max borrow LTV percent is zero') + } // get and update borrow position state const proxy = Address.fromBytes(position.proxy) - const borrowState = createOrLoadAavePosition(proxy) - updateAavePosition(borrowState) + let borrowState: AavePosition + let _borrowState = loadAavePosition(proxy) + if (_borrowState === null) { + borrowState = createOrLoadAavePosition(proxy) + updateAavePosition(borrowState) + } else { + borrowState = _borrowState + } const borrowedAssets = borrowState.borrowedAssets const suppliedOsTokenShares = borrowState.suppliedOsTokenShares @@ -108,8 +116,7 @@ export function updateLeverageStrategyPosition(osToken: OsToken, position: Lever const wad = BigInt.fromString(WAD) if (borrowedAssets.ge(stakedAssets)) { - const borrowLtv = aaveLeverageStrategy.getBorrowLtv() - const leftOsTokenAssets = borrowedAssets.minus(stakedAssets).times(wad).div(borrowLtv) + const leftOsTokenAssets = borrowedAssets.minus(stakedAssets).times(wad).div(aave.leverageMaxBorrowLtvPercent) position.assets = BigInt.zero() position.osTokenShares = suppliedOsTokenShares .minus(mintedOsTokenShares) @@ -148,6 +155,7 @@ export function updateLeverageStrategyPosition(osToken: OsToken, position: Lever export function updateLeverageStrategyPositions( network: Network, + aave: Aave, osToken: OsToken, distributor: Distributor, vault: Vault, @@ -162,7 +170,7 @@ export function updateLeverageStrategyPositions( const assetsBefore = position.assets.plus(position.exitingAssets) const totalAssetsBefore = position.totalAssets - updateLeverageStrategyPosition(osToken, position) + updateLeverageStrategyPosition(aave, osToken, position) const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) const assetsAfter = position.assets.plus(position.exitingAssets) diff --git a/src/entities/osTokenConfig.ts b/src/entities/osTokenConfig.ts index 3529ca3..3d3998a 100644 --- a/src/entities/osTokenConfig.ts +++ b/src/entities/osTokenConfig.ts @@ -25,6 +25,7 @@ export function createOrLoadOsTokenConfig(version: string): OsTokenConfig { osTokenConfig.ltvPercent = BigInt.zero() osTokenConfig.liqThresholdPercent = BigInt.zero() } + osTokenConfig.leverageMaxMintLtvPercent = BigInt.zero() osTokenConfig.save() return osTokenConfig diff --git a/src/entities/osTokenVaultEscrow.ts b/src/entities/osTokenVaultEscrow.ts index ab1a354..f76cd26 100644 --- a/src/entities/osTokenVaultEscrow.ts +++ b/src/entities/osTokenVaultEscrow.ts @@ -1,10 +1,13 @@ -import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts' +import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { OsToken, OsTokenExitRequest, Vault } from '../../generated/schema' import { convertOsTokenSharesToAssets } from './osToken' import { OS_TOKEN_VAULT_ESCROW } from '../helpers/constants' -import { createOrLoadV2Pool } from './v2pool' -import { OsTokenVaultEscrow } from '../../generated/OsTokenVaultEscrow/OsTokenVaultEscrow' +import { loadV2Pool } from './v2pool' import { loadExitRequest } from './exitRequest' +import { getUpdateStateCall } from './vault' +import { chunkedMulticall } from '../helpers/utils' + +const getPositionSelector = '0x3adbb5af' export function createOrLoadOsTokenExitRequest(vault: Address, positionTicket: BigInt): OsTokenExitRequest { const exitRequestId = `${vault.toHex()}-${positionTicket.toString()}` @@ -44,17 +47,20 @@ export function getExitRequestLtv(osTokenExitRequest: OsTokenExitRequest, osToke } export function updateOsTokenExitRequests(osToken: OsToken, vault: Vault): void { - if (vault.isGenesis) { - const v2Pool = createOrLoadV2Pool() - if (!v2Pool.migrated) { - // wait for the migration - return - } + if (vault.isGenesis && !loadV2Pool()!.migrated) { + // wait for the migration + return } const vaultAddress = Address.fromString(vault.id) - const osTokenVaultEscrow = OsTokenVaultEscrow.bind(OS_TOKEN_VAULT_ESCROW) + const updateStateCall: Bytes | null = getUpdateStateCall(vault) + + let contractAddresses: Array
= [] + let contractCalls: Array = [] + if (updateStateCall) { + contractAddresses.push(vaultAddress) + contractCalls.push(updateStateCall) + } - // TODO: move to multicall let osTokenExitRequest: OsTokenExitRequest const osTokenExitRequests: Array = vault.osTokenExitRequests.load() for (let i = 0; i < osTokenExitRequests.length; i++) { @@ -62,9 +68,36 @@ export function updateOsTokenExitRequests(osToken: OsToken, vault: Vault): void if (osTokenExitRequest.osTokenShares.isZero()) { continue } - const response = osTokenVaultEscrow.getPosition(vaultAddress, osTokenExitRequest.positionTicket) - osTokenExitRequest.osTokenShares = response.getValue2() + contractAddresses.push(OS_TOKEN_VAULT_ESCROW) + contractCalls.push(_getPositionCall(vaultAddress, osTokenExitRequest.positionTicket)) + } + + let result = chunkedMulticall(contractAddresses, contractCalls) + if (updateStateCall) { + result = result.slice(1) + } + + // process result + for (let i = 0; i < osTokenExitRequests.length; i++) { + osTokenExitRequest = osTokenExitRequests[i] + if (osTokenExitRequest.osTokenShares.isZero()) { + continue + } + let decodedResult = ethereum.decode('(address,uint256,uint256)', result[i]!)!.toTuple() + osTokenExitRequest.osTokenShares = decodedResult[2].toBigInt() osTokenExitRequest.ltv = getExitRequestLtv(osTokenExitRequest, osToken) osTokenExitRequest.save() } } + +function _getPositionCall(vault: Address, positionTicket: BigInt): Bytes { + const positionCallArray: Array = [ + ethereum.Value.fromAddress(vault), + ethereum.Value.fromUnsignedBigInt(positionTicket), + ] + // Encode the tuple + const encodedPositionCallArgs = ethereum.encode( + ethereum.Value.fromTuple(changetype(positionCallArray)), + ) + return Bytes.fromHexString(getPositionSelector).concat(encodedPositionCallArgs!) +} diff --git a/src/entities/rewardSplitter.ts b/src/entities/rewardSplitter.ts index 9077a7a..e2c3352 100644 --- a/src/entities/rewardSplitter.ts +++ b/src/entities/rewardSplitter.ts @@ -7,10 +7,10 @@ import { RewardSplitterShareHolder, Vault, } from '../../generated/schema' -import { RewardSplitter as RewardSplitterContract } from '../../generated/Keeper/RewardSplitter' import { convertSharesToAssets } from './vault' -import { createOrLoadV2Pool } from './v2pool' +import { loadV2Pool } from './v2pool' import { createOrLoadAllocator, snapshotAllocator } from './allocator' +import { chunkedRewardSplitterMulticall } from '../helpers/utils' const vaultUpdateStateSelector = '0x79c702ad' const syncRewardsCallSelector = '0x72c0c211' @@ -46,12 +46,9 @@ export function updateRewardSplitters( vault: Vault, timestamp: BigInt, ): void { - if (vault.isGenesis) { - const v2Pool = createOrLoadV2Pool() - if (!v2Pool.migrated) { - // wait for the migration - return - } + if (vault.isGenesis && !loadV2Pool()!.migrated) { + // wait for the migration + return } const rewardSplitters: Array = vault.rewardSplitters.load() @@ -71,16 +68,15 @@ export function updateRewardSplitters( calls.push(_getRewardsOfCall(Address.fromBytes(shareHolders[j].address))) } - const rewardSplitterContract = RewardSplitterContract.bind(Address.fromString(rewardSplitter.id)) - let callResult: Array = rewardSplitterContract.multicall(calls) - callResult = callResult.slice(updateStateCall ? 2 : 1) + let result = chunkedRewardSplitterMulticall(Address.fromString(rewardSplitter.id), calls) + result = result.slice(updateStateCall ? 2 : 1) let shareHolder: RewardSplitterShareHolder let earnedVaultAssetsBefore: BigInt for (let j = 0; j < shareHolders.length; j++) { shareHolder = shareHolders[j] earnedVaultAssetsBefore = shareHolder.earnedVaultAssets - shareHolder.earnedVaultShares = ethereum.decode('uint256', callResult[j])!.toBigInt() + shareHolder.earnedVaultShares = ethereum.decode('uint256', result[j])!.toBigInt() shareHolder.earnedVaultAssets = convertSharesToAssets(vault, shareHolder.earnedVaultShares) shareHolder.save() snapshotRewardSplitterShareHolder( diff --git a/src/entities/v2pool.ts b/src/entities/v2pool.ts index f96c9f7..f4add2d 100644 --- a/src/entities/v2pool.ts +++ b/src/entities/v2pool.ts @@ -1,8 +1,7 @@ import { Address, BigDecimal, BigInt, Bytes, ethereum, log } from '@graphprotocol/graph-ts' import { V2Pool, V2PoolUser, Vault } from '../../generated/schema' -import { MULTICALL, V2_POOL_FEE_PERCENT, V2_REWARD_TOKEN, V2_STAKED_TOKEN, WAD } from '../helpers/constants' -import { Multicall as MulticallContract, TryAggregateCallReturnDataStruct } from '../../generated/Keeper/Multicall' -import { calculateAverage, getAggregateCall } from '../helpers/utils' +import { V2_POOL_FEE_PERCENT, V2_REWARD_TOKEN, V2_STAKED_TOKEN, WAD } from '../helpers/constants' +import { calculateAverage, chunkedMulticall } from '../helpers/utils' import { getUpdateStateCall } from './vault' const snapshotsPerWeek = 14 @@ -93,33 +92,38 @@ export function getV2PoolState(vault: Vault): Array { const vaultAddress = Address.fromString(vault.id) const wad = BigInt.fromString(WAD) - const multicallContract = MulticallContract.bind(Address.fromString(MULTICALL)) - let calls: Array = [] + const contractAddresses: Array
= [] + let contractCalls: Array = [] if (updateStateCall) { - calls.push(getAggregateCall(vaultAddress, updateStateCall)) + contractAddresses.push(vaultAddress) + contractCalls.push(updateStateCall) } - calls.push(getAggregateCall(V2_REWARD_TOKEN, rewardAssetsCall)) - calls.push(getAggregateCall(V2_REWARD_TOKEN, penaltyAssetsCall)) - calls.push(getAggregateCall(V2_STAKED_TOKEN, principalAssetsCall)) - calls.push(getAggregateCall(V2_REWARD_TOKEN, rewardPerTokenCall)) - - const result = multicallContract.call('tryAggregate', 'tryAggregate(bool,(address,bytes)[]):((bool,bytes)[])', [ - ethereum.Value.fromBoolean(false), - ethereum.Value.fromArray(calls), - ]) - let resultValue = result[0].toTupleArray() + contractAddresses.push(V2_REWARD_TOKEN) + contractCalls.push(rewardAssetsCall) + + contractAddresses.push(V2_REWARD_TOKEN) + contractCalls.push(penaltyAssetsCall) + + contractAddresses.push(V2_STAKED_TOKEN) + contractCalls.push(principalAssetsCall) + + contractAddresses.push(V2_REWARD_TOKEN) + contractCalls.push(rewardPerTokenCall) + + let results = chunkedMulticall(contractAddresses, contractCalls, false) if (updateStateCall) { - if (!resultValue[0].success) { + // check whether update state call succeeded + if (results[0] === null) { log.error('[Vault] getV2PoolState failed updateStateCall={}', [updateStateCall.toHexString()]) assert(false, 'getV2PoolState failed') } - resultValue = resultValue.slice(1) + results = results.slice(1) } - const rewardAssets = ethereum.decode('uint256', resultValue[0].returnData)!.toBigInt() - const penaltyAssets = ethereum.decode('uint256', resultValue[1].returnData)!.toBigInt() - const principalAssets = ethereum.decode('uint256', resultValue[2].returnData)!.toBigInt() - const rewardRate = ethereum.decode('uint256', resultValue[3].returnData)!.toBigInt() + const rewardAssets = ethereum.decode('uint256', results[0]!)!.toBigInt() + const penaltyAssets = ethereum.decode('uint256', results[1]!)!.toBigInt() + const principalAssets = ethereum.decode('uint256', results[2]!)!.toBigInt() + const rewardRate = ethereum.decode('uint256', results[3]!)!.toBigInt() const totalAssets = principalAssets.plus(rewardAssets) let penaltyRate = BigInt.zero() if (totalAssets.gt(BigInt.zero())) { diff --git a/src/entities/vault.ts b/src/entities/vault.ts index b88e07b..87b6dc0 100644 --- a/src/entities/vault.ts +++ b/src/entities/vault.ts @@ -12,7 +12,6 @@ import { AAVE_LEVERAGE_STRATEGY, AAVE_LEVERAGE_STRATEGY_START_BLOCK, MAX_VAULT_APY, - MULTICALL, VAULT_FACTORY_V2, VAULT_FACTORY_V3, WAD, @@ -20,8 +19,7 @@ import { import { convertAssetsToOsTokenShares, convertOsTokenSharesToAssets, getOsTokenApy } from './osToken' import { isGnosisNetwork, loadNetwork } from './network' import { getV2PoolState, loadV2Pool, updatePoolApy } from './v2pool' -import { Multicall as MulticallContract, TryAggregateCallReturnDataStruct } from '../../generated/Keeper/Multicall' -import { calculateAverage, getAggregateCall, getAnnualReward } from '../helpers/utils' +import { calculateAverage, chunkedMulticall, getAnnualReward } from '../helpers/utils' import { createOrLoadOwnMevEscrow } from './mevEscrow' import { VaultCreated } from '../../generated/templates/VaultFactory/VaultFactory' import { @@ -32,7 +30,6 @@ import { Vault as VaultTemplate, } from '../../generated/templates' import { createTransaction } from './transaction' -import { AaveLeverageStrategy as AaveLeverageStrategyContract } from '../../generated/PeriodicTasks/AaveLeverageStrategy' import { getAaveBorrowApy, getAaveSupplyApy } from './aave' import { getAllocatorId } from './allocator' import { convertStringToDistributionType, DistributionType, getPeriodicDistributionApy } from './merkleDistributor' @@ -379,14 +376,25 @@ export function updateVaultMaxBoostApy( return } const wad = BigInt.fromString(WAD) - const aaveLeverageStrategyContract = AaveLeverageStrategyContract.bind(AAVE_LEVERAGE_STRATEGY) const osTokenApy = getOsTokenApy(osToken, false) const borrowApy = getAaveBorrowApy(aave, false) const supplyApy = getAaveSupplyApy(aave, osToken, false) - const vaultAddress = Address.fromString(vault.id) - const vaultLeverageLtv = aaveLeverageStrategyContract.getVaultLtv(vaultAddress) + const vaultLeverageLtv = osTokenConfig.ltvPercent.lt(osTokenConfig.leverageMaxMintLtvPercent) + ? osTokenConfig.ltvPercent + : osTokenConfig.leverageMaxMintLtvPercent + const aaveLeverageLtv = aave.leverageMaxBorrowLtvPercent + if (vaultLeverageLtv.isZero() || aaveLeverageLtv.isZero()) { + log.warning('[Vault] updateVaultMaxBoostApy ltvPercent is zero vault={} vaultLeverageLtv={} aaveLeverageLtv={}', [ + vault.id, + vaultLeverageLtv.toString(), + aaveLeverageLtv.toString(), + ]) + vault.allocatorMaxBoostApy = BigDecimal.zero() + vault.osTokenHolderMaxBoostApy = BigDecimal.zero() + vault.save() + } // calculate vault staking rate and the rate paid for minting osToken const vaultApy = getVaultApy(vault, false) @@ -404,10 +412,11 @@ export function updateVaultMaxBoostApy( const osTokenHolderOsTokenShares = allocatorMintedOsTokenShares // calculate assets/shares boosted from the strategy - const strategyMintedOsTokenShares = aaveLeverageStrategyContract.getFlashloanOsTokenShares( - vaultAddress, - osTokenHolderOsTokenShares, - ) + const totalLtv = vaultLeverageLtv.times(aaveLeverageLtv).div(wad) + const strategyMintedOsTokenShares = osTokenHolderOsTokenShares + .times(wad) + .div(wad.minus(totalLtv)) + .minus(osTokenHolderOsTokenShares) const strategyMintedOsTokenAssets = convertOsTokenSharesToAssets(osToken, strategyMintedOsTokenShares) const strategyDepositedAssets = strategyMintedOsTokenAssets.times(wad).div(vaultLeverageLtv) @@ -503,41 +512,36 @@ function getVaultState(vault: Vault): Array { } const isV2OrHigherVault = vault.version.ge(BigInt.fromI32(2)) - const vaultAddr = Address.fromString(vault.id) const updateStateCall = getUpdateStateCall(vault) - const convertToAssetsCall = _getConvertToAssetsCall(BigInt.fromString(WAD)) - const totalAssetsCall = Bytes.fromHexString(totalAssetsSelector) - const totalSharesCall = Bytes.fromHexString(totalSharesSelector) - const exitingAssetsCall = Bytes.fromHexString(exitingAssetsSelector) - const queuedSharesCall = Bytes.fromHexString(queuedSharesSelector) - - const multicallContract = MulticallContract.bind(Address.fromString(MULTICALL)) - let calls: Array = [] + + let contractCalls: Array = [] if (updateStateCall) { const getFeeRecipientSharesCall = _getSharesCall(Address.fromBytes(vault.feeRecipient)) - const getFeeRecipientSharesAggregateCall = getAggregateCall(vaultAddr, getFeeRecipientSharesCall) - calls.push(getFeeRecipientSharesAggregateCall) - calls.push(getAggregateCall(vaultAddr, updateStateCall)) - calls.push(getFeeRecipientSharesAggregateCall) + contractCalls.push(getFeeRecipientSharesCall) + contractCalls.push(updateStateCall) + contractCalls.push(getFeeRecipientSharesCall) } - calls.push(getAggregateCall(vaultAddr, convertToAssetsCall)) - calls.push(getAggregateCall(vaultAddr, totalAssetsCall)) - calls.push(getAggregateCall(vaultAddr, totalSharesCall)) - calls.push(getAggregateCall(vaultAddr, queuedSharesCall)) + contractCalls.push(_getConvertToAssetsCall(BigInt.fromString(WAD))) + contractCalls.push(Bytes.fromHexString(totalAssetsSelector)) + contractCalls.push(Bytes.fromHexString(totalSharesSelector)) + contractCalls.push(Bytes.fromHexString(queuedSharesSelector)) if (isV2OrHigherVault) { - calls.push(getAggregateCall(vaultAddr, exitingAssetsCall)) + contractCalls.push(Bytes.fromHexString(exitingAssetsSelector)) + } + + const callsCount = contractCalls.length + let contractAddresses = new Array
(callsCount) + const vaultAddr = Address.fromString(vault.id) + for (let i = 0; i < callsCount; i++) { + contractAddresses[i] = vaultAddr } - const result = multicallContract.call('tryAggregate', 'tryAggregate(bool,(address,bytes)[]):((bool,bytes)[])', [ - ethereum.Value.fromBoolean(false), - ethereum.Value.fromArray(calls), - ]) - let resultValue = result[0].toTupleArray() + let results = chunkedMulticall(contractAddresses, contractCalls, false) let feeRecipientEarnedShares = BigInt.zero() if (updateStateCall) { // check whether update state call succeeded - if (!resultValue[1].success) { + if (results[1] === null) { log.error('[Vault] getVaultState failed for vault={} updateStateCall={}', [ vault.id, updateStateCall.toHexString(), @@ -546,21 +550,19 @@ function getVaultState(vault: Vault): Array { } // calculate fee recipient earned shares - const feeRecipientSharesBefore = ethereum.decode('uint256', resultValue[0].returnData)!.toBigInt() - const feeRecipientSharesAfter = ethereum.decode('uint256', resultValue[2].returnData)!.toBigInt() + const feeRecipientSharesBefore = ethereum.decode('uint256', results[0]!)!.toBigInt() + const feeRecipientSharesAfter = ethereum.decode('uint256', results[2]!)!.toBigInt() feeRecipientEarnedShares = feeRecipientSharesAfter.minus(feeRecipientSharesBefore) // remove responses from the result - resultValue = resultValue.slice(3) + results = results.slice(3) } - const newRate = ethereum.decode('uint256', resultValue[0].returnData)!.toBigInt() - const totalAssets = ethereum.decode('uint256', resultValue[1].returnData)!.toBigInt() - const totalShares = ethereum.decode('uint256', resultValue[2].returnData)!.toBigInt() - const queuedShares = ethereum.decode('uint128', resultValue[3].returnData)!.toBigInt() - const exitingAssets = isV2OrHigherVault - ? ethereum.decode('uint128', resultValue[4].returnData)!.toBigInt() - : vault.exitingAssets + const newRate = ethereum.decode('uint256', results[0]!)!.toBigInt() + const totalAssets = ethereum.decode('uint256', results[1]!)!.toBigInt() + const totalShares = ethereum.decode('uint256', results[2]!)!.toBigInt() + const queuedShares = ethereum.decode('uint128', results[3]!)!.toBigInt() + const exitingAssets = isV2OrHigherVault ? ethereum.decode('uint128', results[4]!)!.toBigInt() : vault.exitingAssets return [newRate, totalAssets, totalShares, queuedShares, exitingAssets, feeRecipientEarnedShares] } diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 1b7390a..40e3c1d 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -1,9 +1,8 @@ import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' - -export function getAggregateCall(target: Address, data: Bytes): ethereum.Value { - const struct: Array = [ethereum.Value.fromAddress(target), ethereum.Value.fromBytes(data)] - return ethereum.Value.fromTuple(changetype(struct)) -} +import { Vault as VaultContract } from '../../generated/Keeper/Vault' +import { Multicall as MulticallContract, TryAggregateCallReturnDataStruct } from '../../generated/Keeper/Multicall' +import { RewardSplitter as RewardSplitterContract } from '../../generated/Keeper/RewardSplitter' +import { MULTICALL } from './constants' export function calculateMedian(values: Array): BigDecimal { if (values.length === 0) { @@ -61,3 +60,81 @@ export function getCompoundedApy(initialApyPercent: BigDecimal, secondaryApyPerc // convert back to a percentage if needed return finalApy.times(hundred) } + +export function chunkedVaultMulticall(vaultAddress: Address, calls: Array, chunkSize: i32 = 10): Array { + const vaultContract = VaultContract.bind(vaultAddress) + let aggregatedResults: Bytes[] = [] + for (let i = 0; i < calls.length; i += chunkSize) { + let chunk = calls.slice(i, i + chunkSize) + let chunkResult = vaultContract.multicall(chunk) + // Concatenate results in order + for (let j = 0; j < chunkResult.length; j++) { + aggregatedResults.push(chunkResult[j]) + } + } + return aggregatedResults +} + +export function chunkedRewardSplitterMulticall( + rewardSplitter: Address, + calls: Array, + chunkSize: i32 = 10, +): Array { + const rewardSplitterContract = RewardSplitterContract.bind(rewardSplitter) + let aggregatedResults: Bytes[] = [] + for (let i = 0; i < calls.length; i += chunkSize) { + let chunk = calls.slice(i, i + chunkSize) + let chunkResult = rewardSplitterContract.multicall(chunk) + // Concatenate results in order + for (let j = 0; j < chunkResult.length; j++) { + aggregatedResults.push(chunkResult[j]) + } + } + return aggregatedResults +} + +export function chunkedMulticall( + contractAddresses: Array
, + contractCalls: Array, + requireSuccess: boolean = true, + chunkSize: i32 = 10, +): Array { + const callsCount = contractAddresses.length + if (callsCount !== contractCalls.length) { + assert(false, 'contractAddresses and calls must have the same length') + } + if (callsCount == 0) { + return [] + } + + const aggregateCalls = new Array(callsCount) + for (let i = 0; i < callsCount; i++) { + aggregateCalls.push(_getAggregateCall(contractAddresses[i], contractCalls[i])) + } + + const multicallContract = MulticallContract.bind(Address.fromString(MULTICALL)) + const encodedRequireSuccess = ethereum.Value.fromBoolean(requireSuccess) + let callResults = new Array(callsCount) + for (let i = 0; i < callsCount; i += chunkSize) { + const chunkCalls = aggregateCalls.slice(i, i + chunkSize) + const chunkResult = multicallContract + .call('tryAggregate', 'tryAggregate(bool,(address,bytes)[]):((bool,bytes)[])', [ + encodedRequireSuccess, + ethereum.Value.fromArray(chunkCalls), + ])[0] + .toTupleArray() + callResults = callResults.concat(chunkResult) + } + + const results = new Array(callsCount) + for (let i = 0; i < callsCount; i += chunkSize) { + const callResult = callResults[i] + results.push(callResult.success ? callResult.returnData : null) + } + return results +} + +export function _getAggregateCall(target: Address, data: Bytes): ethereum.Value { + const struct: Array = [ethereum.Value.fromAddress(target), ethereum.Value.fromBytes(data)] + return ethereum.Value.fromTuple(changetype(struct)) +} diff --git a/src/mappings/exchangeRates.ts b/src/mappings/exchangeRates.ts index 5896ad0..c115e19 100644 --- a/src/mappings/exchangeRates.ts +++ b/src/mappings/exchangeRates.ts @@ -1,157 +1,24 @@ -import { Address, BigDecimal, BigInt, ethereum, log } from '@graphprotocol/graph-ts' -import { PriceFeed as PriceFeedContract } from '../../generated/ExchangeRates/PriceFeed' -import { - ASSETS_USD_PRICE_FEED, - EUR_USD_PRICE_FEED, - GBP_USD_PRICE_FEED, - CNY_USD_PRICE_FEED, - JPY_USD_PRICE_FEED, - KRW_USD_PRICE_FEED, - AUD_USD_PRICE_FEED, - DAI_USD_PRICE_FEED, - USDC_USD_PRICE_FEED, - SWISE_ASSET_UNI_POOL, -} from '../helpers/constants' -import { ExchangeRateSnapshot, UniswapPool } from '../../generated/schema' +import { ethereum, log } from '@graphprotocol/graph-ts' import { loadNetwork } from '../entities/network' +import { updateExchangeRates } from '../entities/exchangeRates' export function handleExchangeRates(block: ethereum.Block): void { - const decimals = BigDecimal.fromString('100000000') - - let assetsUsdRate = BigDecimal.zero() - let swiseUsdRate = BigDecimal.zero() - let daiUsdRate = BigDecimal.zero() - let usdcUsdRate = BigDecimal.zero() - let eurToUsdRate = BigDecimal.zero() - let gbpToUsdRate = BigDecimal.zero() - let cnyToUsdRate = BigDecimal.zero() - let jpyToUsdRate = BigDecimal.zero() - let krwToUsdRate = BigDecimal.zero() - let audToUsdRate = BigDecimal.zero() - let response: BigInt - let priceFeedContract: PriceFeedContract - - const assetsUsdPriceFeed = Address.fromString(ASSETS_USD_PRICE_FEED) - if (assetsUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(assetsUsdPriceFeed) - response = priceFeedContract.latestAnswer() - assetsUsdRate = new BigDecimal(response).div(decimals) - } - - const eurUsdPriceFeed = Address.fromString(EUR_USD_PRICE_FEED) - if (eurUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(eurUsdPriceFeed) - response = priceFeedContract.latestAnswer() - eurToUsdRate = new BigDecimal(response).div(decimals) - } - - const gbpUsdPriceFeed = Address.fromString(GBP_USD_PRICE_FEED) - if (gbpUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(gbpUsdPriceFeed) - response = priceFeedContract.latestAnswer() - gbpToUsdRate = new BigDecimal(response).div(decimals) - } - - const cnyUsdPriceFeed = Address.fromString(CNY_USD_PRICE_FEED) - if (cnyUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(cnyUsdPriceFeed) - response = priceFeedContract.latestAnswer() - cnyToUsdRate = new BigDecimal(response).div(decimals) - } - - const jpyUsdPriceFeed = Address.fromString(JPY_USD_PRICE_FEED) - if (jpyUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(jpyUsdPriceFeed) - response = priceFeedContract.latestAnswer() - jpyToUsdRate = new BigDecimal(response).div(decimals) - } - - const krwUsdPriceFeed = Address.fromString(KRW_USD_PRICE_FEED) - if (krwUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(krwUsdPriceFeed) - response = priceFeedContract.latestAnswer() - krwToUsdRate = new BigDecimal(response).div(decimals) - } - - const audUsdPriceFeed = Address.fromString(AUD_USD_PRICE_FEED) - if (audUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(audUsdPriceFeed) - response = priceFeedContract.latestAnswer() - audToUsdRate = new BigDecimal(response).div(decimals) - } - - const daiUsdPriceFeed = Address.fromString(DAI_USD_PRICE_FEED) - if (daiUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(daiUsdPriceFeed) - response = priceFeedContract.latestAnswer() - daiUsdRate = new BigDecimal(response).div(decimals) - } - - const usdcUsdPriceFeed = Address.fromString(USDC_USD_PRICE_FEED) - if (usdcUsdPriceFeed.notEqual(Address.zero())) { - priceFeedContract = PriceFeedContract.bind(usdcUsdPriceFeed) - response = priceFeedContract.latestAnswer() - usdcUsdRate = new BigDecimal(response).div(decimals) - } - - const swiseAssetUniPool = Address.fromString(SWISE_ASSET_UNI_POOL) - if (swiseAssetUniPool.notEqual(Address.zero())) { - const pool = UniswapPool.load(swiseAssetUniPool.toHex()) - if (pool !== null) { - const swiseAssetRate = new BigDecimal(pool.sqrtPrice.pow(2)).div(new BigDecimal(BigInt.fromI32(2).pow(192))) - swiseUsdRate = swiseAssetRate.times(assetsUsdRate) - } - } - - const zero = BigDecimal.zero() - const one = BigDecimal.fromString('1') - const usdToEurRate = eurToUsdRate.gt(zero) ? one.div(eurToUsdRate) : zero - const usdToGbpRate = gbpToUsdRate.gt(zero) ? one.div(gbpToUsdRate) : zero - const usdToCnyRate = cnyToUsdRate.gt(zero) ? one.div(cnyToUsdRate) : zero - const usdToJpyRate = jpyToUsdRate.gt(zero) ? one.div(jpyToUsdRate) : zero - const usdToKrwRate = krwToUsdRate.gt(zero) ? one.div(krwToUsdRate) : zero - const usdToAudRate = audToUsdRate.gt(zero) ? one.div(audToUsdRate) : zero - const network = loadNetwork()! - network.assetsUsdRate = assetsUsdRate - network.swiseUsdRate = swiseUsdRate - network.usdToEurRate = usdToEurRate - network.usdToGbpRate = usdToGbpRate - network.usdToCnyRate = usdToCnyRate - network.usdToJpyRate = usdToJpyRate - network.usdToKrwRate = usdToKrwRate - network.usdToAudRate = usdToAudRate - network.daiUsdRate = daiUsdRate - network.usdcUsdRate = usdcUsdRate - network.save() - - const exchangeRateSnapshot = new ExchangeRateSnapshot(block.timestamp.toString()) - exchangeRateSnapshot.timestamp = block.timestamp.toI64() - exchangeRateSnapshot.assetsUsdRate = assetsUsdRate - exchangeRateSnapshot.swiseUsdRate = swiseUsdRate - exchangeRateSnapshot.daiUsdRate = daiUsdRate - exchangeRateSnapshot.usdcUsdRate = usdcUsdRate - exchangeRateSnapshot.usdToEurRate = usdToEurRate - exchangeRateSnapshot.usdToGbpRate = usdToGbpRate - exchangeRateSnapshot.usdToCnyRate = usdToCnyRate - exchangeRateSnapshot.usdToJpyRate = usdToJpyRate - exchangeRateSnapshot.usdToKrwRate = usdToKrwRate - exchangeRateSnapshot.usdToAudRate = usdToAudRate - exchangeRateSnapshot.save() + updateExchangeRates(network, block.timestamp) log.info( '[ExchangeRates] assetsUsdRate={} usdToEurRate={} usdToGbpRate={} usdToCnyRate={} usdToJpyRate={} usdToKrwRate={} usdToAudRate={} daiUsdRate={} usdcUsdRate={} swiseUsdRate={}', [ - assetsUsdRate.toString(), - usdToEurRate.toString(), - usdToGbpRate.toString(), - usdToCnyRate.toString(), - usdToJpyRate.toString(), - usdToKrwRate.toString(), - usdToAudRate.toString(), - daiUsdRate.toString(), - usdcUsdRate.toString(), - swiseUsdRate.toString(), + network.assetsUsdRate.toString(), + network.usdToEurRate.toString(), + network.usdToGbpRate.toString(), + network.usdToCnyRate.toString(), + network.usdToJpyRate.toString(), + network.usdToKrwRate.toString(), + network.usdToAudRate.toString(), + network.daiUsdRate.toString(), + network.usdcUsdRate.toString(), + network.swiseUsdRate.toString(), ], ) } diff --git a/src/mappings/keeper.ts b/src/mappings/keeper.ts index 7718b37..3e5340d 100644 --- a/src/mappings/keeper.ts +++ b/src/mappings/keeper.ts @@ -26,7 +26,7 @@ import { RewardSplitterFactory as RewardSplitterFactoryTemplate, VaultFactory as VaultFactoryTemplate, } from '../../generated/templates' -import { AavePosition, Allocator, OsTokenHolder } from '../../generated/schema' +import { Allocator, OsTokenHolder } from '../../generated/schema' import { createOrLoadOsToken, loadOsToken, @@ -58,7 +58,7 @@ import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' import { loadVault, updateVaultMaxBoostApy, updateVaults } from '../entities/vault' import { getOsTokenHolderApy, snapshotOsTokenHolder, updateOsTokenHolderAssets } from '../entities/osTokenHolder' -import { createOrLoadAave, loadAave, updateAavePosition } from '../entities/aave' +import { createOrLoadAave, loadAave } from '../entities/aave' import { createOrLoadDistributor, loadDistributor } from '../entities/merkleDistributor' const IS_PRIVATE_KEY = 'isPrivate' @@ -227,12 +227,8 @@ export function handleRewardsUpdated(event: RewardsUpdated): void { } const feeRecipientsEarnedShares = updateVaults(json.fromBytes(data!), rewardsRoot, updateTimestamp, rewardsIpfsHash) - // update Aave + // fetch Aave data const aave = loadAave()! - const positions: Array = aave.positions.load() - for (let i = 0; i < positions.length; i++) { - updateAavePosition(positions[i]) - } // update OsToken const osToken = loadOsToken()! @@ -304,7 +300,7 @@ export function handleRewardsUpdated(event: RewardsUpdated): void { updateOsTokenExitRequests(osToken, vault) // update leverage strategy positions - updateLeverageStrategyPositions(network, osToken, distributor, vault, osTokenConfig, blockTimestamp) + updateLeverageStrategyPositions(network, aave, osToken, distributor, vault, osTokenConfig, blockTimestamp) for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] diff --git a/src/mappings/leverageStrategy.ts b/src/mappings/leverageStrategy.ts index c851422..2dc924a 100644 --- a/src/mappings/leverageStrategy.ts +++ b/src/mappings/leverageStrategy.ts @@ -25,7 +25,7 @@ import { getOsTokenHolderApy, loadOsTokenHolder } from '../entities/osTokenHolde import { loadNetwork } from '../entities/network' import { loadVault } from '../entities/vault' import { loadOsTokenConfig } from '../entities/osTokenConfig' -import { createOrLoadAavePosition } from '../entities/aave' +import { createOrLoadAavePosition, loadAave } from '../entities/aave' import { loadDistributor } from '../entities/merkleDistributor' function _updateAllocatorAndOsTokenHolderApys( @@ -36,10 +36,11 @@ function _updateAllocatorAndOsTokenHolderApys( vault: Vault, userAddress: Address, ): void { - const allocator = loadAllocator(userAddress, Address.fromString(vault.id))! - allocator.apy = getAllocatorApy(osToken, osTokenConfig, vault, distributor, allocator, false) - allocator.save() - + const allocator = loadAllocator(userAddress, Address.fromString(vault.id)) + if (allocator) { + allocator.apy = getAllocatorApy(osToken, osTokenConfig, vault, distributor, allocator, false) + allocator.save() + } const osTokenHolder = loadOsTokenHolder(userAddress)! osTokenHolder.apy = getOsTokenHolderApy(network, osToken, distributor, osTokenHolder, false) osTokenHolder.save() @@ -70,6 +71,7 @@ export function handleDeposited(event: Deposited): void { const depositedOsTokenShares = event.params.osTokenShares const timestamp = event.block.timestamp + const aave = loadAave()! const network = loadNetwork()! const osToken = loadOsToken()! const distributor = loadDistributor()! @@ -92,7 +94,7 @@ export function handleDeposited(event: Deposited): void { const assetsBefore = position.assets.plus(position.exitingAssets) const totalAssetsBefore = position.totalAssets - updateLeverageStrategyPosition(osToken, position) + updateLeverageStrategyPosition(aave, osToken, position) const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) const assetsAfter = position.assets.plus(position.exitingAssets) @@ -146,6 +148,7 @@ export function handleExitQueueEntered(event: ExitQueueEntered): void { const exitingPercent = event.params.positionPercent const timestamp = event.block.timestamp + const aave = loadAave()! const osToken = loadOsToken()! const network = loadNetwork()! const vault = loadVault(vaultAddress)! @@ -170,7 +173,7 @@ export function handleExitQueueEntered(event: ExitQueueEntered): void { position.exitRequest = `${vaultAddressHex}-${positionTicket}` position.exitingPercent = exitingPercent - updateLeverageStrategyPosition(osToken, position) + updateLeverageStrategyPosition(aave, osToken, position) const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) const assetsAfter = position.assets.plus(position.exitingAssets) @@ -214,6 +217,7 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { const osToken = loadOsToken()! const network = loadNetwork()! + const aave = loadAave()! const distributor = loadDistributor()! const vault = loadVault(vaultAddress)! const osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! @@ -237,7 +241,7 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { const assetsBefore = position.assets.plus(position.exitingAssets) const totalAssetsBefore = position.totalAssets - updateLeverageStrategyPosition(osToken, position) + updateLeverageStrategyPosition(aave, osToken, position) const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) const assetsAfter = position.assets.plus(position.exitingAssets) diff --git a/src/mappings/osTokenConfig.ts b/src/mappings/osTokenConfig.ts index 183a9a0..6a53d78 100644 --- a/src/mappings/osTokenConfig.ts +++ b/src/mappings/osTokenConfig.ts @@ -4,6 +4,7 @@ import { OsTokenConfigUpdated as OsTokenConfigV2Updated } from '../../generated/ import { updateAllocatorsLtvStatus } from '../entities/allocator' import { createOrLoadOsTokenConfig } from '../entities/osTokenConfig' import { loadVault } from '../entities/vault' +import { Vault } from '../../generated/schema' export function handleOsTokenConfigV1Updated(event: OsTokenConfigV1Updated): void { const ltvPercent = event.params.ltvPercent @@ -41,10 +42,13 @@ export function handleOsTokenConfigV2Updated(event: OsTokenConfigV2Updated): voi function updateOsTokenConfig(version: string, ltvPercent: BigInt, liqThresholdPercent: BigInt): void { const osTokenConfig = createOrLoadOsTokenConfig(version) - osTokenConfig.ltvPercent = ltvPercent osTokenConfig.liqThresholdPercent = liqThresholdPercent + if (Vault.load(version) !== null) { + // vault specific config + osTokenConfig.leverageMaxMintLtvPercent = createOrLoadOsTokenConfig('2').leverageMaxMintLtvPercent + } osTokenConfig.save() log.info('[OsTokenConfig] OsTokenConfigUpdated version={} ltvPercent={} liqThresholdPercent={}', [ diff --git a/src/mappings/periodicTasks.ts b/src/mappings/periodicTasks.ts index afa28ba..17f3113 100644 --- a/src/mappings/periodicTasks.ts +++ b/src/mappings/periodicTasks.ts @@ -2,7 +2,7 @@ import { Address, BigInt, ethereum, log } from '@graphprotocol/graph-ts' import { loadVault, snapshotVault, updateVaultMaxBoostApy } from '../entities/vault' import { loadOsToken, snapshotOsToken, updateOsTokenTotalAssets } from '../entities/osToken' import { loadNetwork } from '../entities/network' -import { AavePosition, Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' +import { Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' import { getAllocatorApy, getAllocatorsMintedShares, @@ -14,7 +14,7 @@ import { getOsTokenHolderApy, snapshotOsTokenHolder, updateOsTokenHolderAssets } import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { loadOsTokenConfig } from '../entities/osTokenConfig' -import { loadAave, updateAaveApys, updateAavePosition } from '../entities/aave' +import { loadAave, updateAaveApys, updateAavePositions } from '../entities/aave' import { loadDistributor, updateDistributions } from '../entities/merkleDistributor' export function handlePeriodicTasks(block: ethereum.Block): void { @@ -29,10 +29,7 @@ export function handlePeriodicTasks(block: ethereum.Block): void { // update Aave // NB! if blocksInHour config is updated, the average apy calculation must be updated updateAaveApys(aave, block.number) - const positions: Array = aave.positions.load() - for (let i = 0; i < positions.length; i++) { - updateAavePosition(positions[i]) - } + updateAavePositions(aave) // update osToken const osToken = loadOsToken()! @@ -81,7 +78,7 @@ export function handlePeriodicTasks(block: ethereum.Block): void { updateOsTokenExitRequests(osToken, vault) // update leverage strategy positions - updateLeverageStrategyPositions(network, osToken, distributor, vault, osTokenConfig, timestamp) + updateLeverageStrategyPositions(network, aave, osToken, distributor, vault, osTokenConfig, timestamp) for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] diff --git a/src/mappings/strategiesRegistry.ts b/src/mappings/strategiesRegistry.ts new file mode 100644 index 0000000..c6123bb --- /dev/null +++ b/src/mappings/strategiesRegistry.ts @@ -0,0 +1,35 @@ +import { StrategyConfigUpdated } from '../../generated/StrategiesRegistry/StrategiesRegistry' +import { Address, ethereum, log } from '@graphprotocol/graph-ts' +import { loadAave } from '../entities/aave' +import { loadOsTokenConfig } from '../entities/osTokenConfig' +import { loadNetwork } from '../entities/network' +import { loadVault } from '../entities/vault' + +export function handleStrategyConfigUpdated(event: StrategyConfigUpdated): void { + const configName = event.params.configName + const value = event.params.value + + if (configName == 'leverageMaxBorrowLtvPercent') { + const aave = loadAave()! + aave.leverageMaxBorrowLtvPercent = ethereum.decode('uint256', value)!.toBigInt() + aave.save() + } else if (configName == 'maxVaultLtvPercent') { + const leverageMaxMinLtvPercent = ethereum.decode('uint256', value)!.toBigInt() + let osTokenConfig = loadOsTokenConfig('2')! + osTokenConfig.leverageMaxMintLtvPercent = leverageMaxMinLtvPercent + osTokenConfig.save() + + const network = loadNetwork()! + const vaultIds = network.osTokenVaultIds + for (let i = 0; i < vaultIds.length; i++) { + const vault = loadVault(Address.fromString(vaultIds[i]))! + if (vault.osTokenConfig != '1' && vault.osTokenConfig != '2') { + osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! + osTokenConfig.leverageMaxMintLtvPercent = leverageMaxMinLtvPercent + osTokenConfig.save() + } + } + } + + log.info('[StrategiesRegistry] StrategyConfigUpdated configName={}', [configName]) +} diff --git a/src/schema.graphql b/src/schema.graphql index 269f466..88bbee3 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -362,9 +362,9 @@ enum AllocatorActionType { OsTokenBurned OsTokenLiquidated OsTokenRedeemed - BoostDeposited, - BoostExitQueueEntered, - BoostExitedAssetsClaimed, + BoostDeposited + BoostExitQueueEntered + BoostExitedAssetsClaimed } """ @@ -653,6 +653,9 @@ type OsTokenConfig @entity { "The vault osToken LTV percent" ltvPercent: BigInt! + "The leverage strategy max OsToken minting LTV" + leverageMaxMintLtvPercent: BigInt! + "The vault osToken liquidation threshold percent" liqThresholdPercent: BigInt! } @@ -702,6 +705,9 @@ type Aave @entity { "The list of OsToken supply APY snapshots" supplyApys: [BigDecimal!]! + "The Aave leverage strategy max borrow LTV percent" + leverageMaxBorrowLtvPercent: BigInt! + "The Aave positions in Leveraged strategy" positions: [AavePosition!]! @derivedFrom(field: "aave") } diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 5d76eeb..f4500bf 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -182,6 +182,8 @@ dataSources: handler: handleOwnershipTransferred - event: RewardsUpdated(indexed address,indexed bytes32,uint256,uint64,uint64,string) handler: handleRewardsUpdated + calls: + osTokenTotalAssets: OsTokenVaultController[{{ osTokenVaultController.address }}].totalAssets() - event: Harvested(indexed address,indexed bytes32,int256,uint256) handler: handleHarvested - event: ValidatorsApproval(indexed address,string) @@ -475,6 +477,8 @@ dataSources: eventHandlers: - event: StrategyProxyCreated(indexed bytes32,indexed address,indexed address,address) handler: handleStrategyProxyCreated + calls: + strategyProxy: AaveLeverageStrategy[event.address].getStrategyProxy(event.params.vault, event.params.user) - event: Deposited(indexed address,indexed address,uint256,uint256,address) handler: handleDeposited - event: ExitQueueEntered(indexed address,indexed address,uint256,uint256,uint256,uint256) @@ -534,6 +538,29 @@ dataSources: handler: handleTransfer calls: uniPosition: UniswapPositionManager[event.address].positions(event.params.tokenId) + - kind: ethereum/contract + name: StrategiesRegistry + network: {{ network }} + source: + address: '{{ strategiesRegistry.address }}' + abi: StrategiesRegistry + startBlock: {{ strategiesRegistry.startBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + file: ./mappings/strategiesRegistry.ts + entities: + - Aave + - OsTokenConfig + - Vault + - Network + abis: + - name: StrategiesRegistry + file: ./abis/StrategiesRegistry.json + eventHandlers: + - event: StrategyConfigUpdated(indexed bytes32,string,bytes) + handler: handleStrategyConfigUpdated templates: - kind: ethereum/contract name: VaultFactory From 24b681724c5d63857b8bef29833ce5cddfd97268 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Fri, 3 Jan 2025 00:22:37 +0200 Subject: [PATCH 02/17] Remove OsTokenVaultController totalAssets call --- src/subgraph.template.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index f4500bf..4e522f0 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -182,8 +182,6 @@ dataSources: handler: handleOwnershipTransferred - event: RewardsUpdated(indexed address,indexed bytes32,uint256,uint64,uint64,string) handler: handleRewardsUpdated - calls: - osTokenTotalAssets: OsTokenVaultController[{{ osTokenVaultController.address }}].totalAssets() - event: Harvested(indexed address,indexed bytes32,int256,uint256) handler: handleHarvested - event: ValidatorsApproval(indexed address,string) From 5e26f271ff5898baa6a45c02c85229603569a1af Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Fri, 3 Jan 2025 13:47:38 +0200 Subject: [PATCH 03/17] Fix multicall arrays --- src/entities/allocator.ts | 8 ++++---- src/entities/vault.ts | 2 +- src/helpers/utils.ts | 26 ++++++++++++++++---------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/entities/allocator.ts b/src/entities/allocator.ts index 0897616..4555e82 100644 --- a/src/entities/allocator.ts +++ b/src/entities/allocator.ts @@ -119,15 +119,15 @@ export function getAllocatorsMintedShares(vault: Vault, allocators: Array(allocatorsCount) + let response: Array = [] for (let i = 0; i < allocatorsCount; i++) { - response[i] = BigInt.zero() + response.push(BigInt.zero()) } return response } // Prepare all calls for retrieving minted shares from OsToken positions - let calls = new Array() + let calls: Array = [] for (let i = 0; i < allocatorsCount; i++) { calls.push(_getOsTokenPositionsCall(allocators[i])) } @@ -136,7 +136,7 @@ export function getAllocatorsMintedShares(vault: Vault, allocators: Array() + let mintedShares: Array = [] for (let i = 0; i < allocatorsCount; i++) { mintedShares.push(ethereum.decode('uint256', results[i])!.toBigInt()) } diff --git a/src/entities/vault.ts b/src/entities/vault.ts index 87b6dc0..d75d9d2 100644 --- a/src/entities/vault.ts +++ b/src/entities/vault.ts @@ -530,7 +530,7 @@ function getVaultState(vault: Vault): Array { } const callsCount = contractCalls.length - let contractAddresses = new Array
(callsCount) + let contractAddresses: Array
= [] const vaultAddr = Address.fromString(vault.id) for (let i = 0; i < callsCount; i++) { contractAddresses[i] = vaultAddr diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 40e3c1d..6d209f0 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -63,9 +63,12 @@ export function getCompoundedApy(initialApyPercent: BigDecimal, secondaryApyPerc export function chunkedVaultMulticall(vaultAddress: Address, calls: Array, chunkSize: i32 = 10): Array { const vaultContract = VaultContract.bind(vaultAddress) - let aggregatedResults: Bytes[] = [] - for (let i = 0; i < calls.length; i += chunkSize) { - let chunk = calls.slice(i, i + chunkSize) + const callsCount = calls.length + + let aggregatedResults: Array = [] + let chunk: Array + for (let i = 0; i < callsCount; i += chunkSize) { + chunk = calls.slice(i, i + chunkSize) let chunkResult = vaultContract.multicall(chunk) // Concatenate results in order for (let j = 0; j < chunkResult.length; j++) { @@ -81,9 +84,12 @@ export function chunkedRewardSplitterMulticall( chunkSize: i32 = 10, ): Array { const rewardSplitterContract = RewardSplitterContract.bind(rewardSplitter) - let aggregatedResults: Bytes[] = [] - for (let i = 0; i < calls.length; i += chunkSize) { - let chunk = calls.slice(i, i + chunkSize) + const callsCount = calls.length + + let aggregatedResults: Array = [] + let chunk: Array + for (let i = 0; i < callsCount; i += chunkSize) { + chunk = calls.slice(i, i + chunkSize) let chunkResult = rewardSplitterContract.multicall(chunk) // Concatenate results in order for (let j = 0; j < chunkResult.length; j++) { @@ -107,14 +113,14 @@ export function chunkedMulticall( return [] } - const aggregateCalls = new Array(callsCount) + const aggregateCalls: Array = [] for (let i = 0; i < callsCount; i++) { aggregateCalls.push(_getAggregateCall(contractAddresses[i], contractCalls[i])) } const multicallContract = MulticallContract.bind(Address.fromString(MULTICALL)) const encodedRequireSuccess = ethereum.Value.fromBoolean(requireSuccess) - let callResults = new Array(callsCount) + let callResults: Array = [] for (let i = 0; i < callsCount; i += chunkSize) { const chunkCalls = aggregateCalls.slice(i, i + chunkSize) const chunkResult = multicallContract @@ -126,8 +132,8 @@ export function chunkedMulticall( callResults = callResults.concat(chunkResult) } - const results = new Array(callsCount) - for (let i = 0; i < callsCount; i += chunkSize) { + const results: Array = [] + for (let i = 0; i < callsCount; i++) { const callResult = callResults[i] results.push(callResult.success ? callResult.returnData : null) } From 0d0989c3a735d762f526f4bdb4edd598454c2dc4 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Fri, 3 Jan 2025 16:45:11 +0200 Subject: [PATCH 04/17] Update nvmrc --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 431076a..f812e45 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.16.0 +20.18.1 \ No newline at end of file From b90c7b16e07d8ef22a9392ac66c0013348282dda Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sat, 4 Jan 2025 01:30:00 +0200 Subject: [PATCH 05/17] Reduce number of snapshots --- src/entities/allocator.ts | 15 ++-- src/entities/exitRequest.ts | 75 ++++++----------- src/entities/leverageStrategy.ts | 137 ++++++++++++++++--------------- src/entities/network.ts | 11 +++ src/entities/osToken.ts | 8 +- src/entities/osTokenHolder.ts | 5 +- src/entities/rewardSplitter.ts | 43 ++-------- src/mappings/gnoVault.ts | 4 +- src/mappings/keeper.ts | 124 +++++++++++++++++----------- src/mappings/leverageStrategy.ts | 94 +++++++-------------- src/mappings/periodicTasks.ts | 44 +++------- src/mappings/rewardSplitter.ts | 17 +--- src/mappings/vault.ts | 19 +---- src/schema.graphql | 12 +++ src/subgraph.template.yaml | 2 + 15 files changed, 263 insertions(+), 347 deletions(-) diff --git a/src/entities/allocator.ts b/src/entities/allocator.ts index 4555e82..1c66736 100644 --- a/src/entities/allocator.ts +++ b/src/entities/allocator.ts @@ -85,6 +85,7 @@ export function createOrLoadAllocator(allocatorAddress: Address, vaultAddress: A vaultAllocator.address = allocatorAddress vaultAllocator.vault = vaultAddress.toHex() vaultAllocator.apy = BigDecimal.zero() + vaultAllocator._periodEarnedAssets = BigInt.zero() vaultAllocator.save() } @@ -326,11 +327,14 @@ export function updateAllocatorMintedOsTokenShares( osTokenConfig: OsTokenConfig, allocator: Allocator, newMintedOsTokenShares: BigInt, -): BigInt { +): void { const mintedOsTokenSharesDiff = newMintedOsTokenShares.minus(allocator.mintedOsTokenShares) - if (osTokenConfig.ltvPercent.isZero()) { - log.error('[Allocator] ltvPercent cannot be zero for vault={}', [allocator.vault]) - return convertOsTokenSharesToAssets(osToken, mintedOsTokenSharesDiff) + if (osTokenConfig.ltvPercent.isZero() || mintedOsTokenSharesDiff.lt(BigInt.zero())) { + log.error( + '[Allocator] minted OsToken shares update failed for allocator={} osTokenConfig={} mintedOsTokenSharesDiff={}', + [allocator.id, osTokenConfig.id, mintedOsTokenSharesDiff.toString()], + ) + return } const mintedOsTokenAssetsDiff = convertOsTokenSharesToAssets(osToken, mintedOsTokenSharesDiff) @@ -340,9 +344,8 @@ export function updateAllocatorMintedOsTokenShares( allocator.mintedOsTokenShares = newMintedOsTokenShares allocator.ltv = getAllocatorLtv(allocator, osToken) allocator.ltvStatus = getAllocatorLtvStatus(allocator, osTokenConfig) + allocator._periodEarnedAssets = allocator._periodEarnedAssets.minus(mintedOsTokenAssetsDiff) allocator.save() - - return mintedOsTokenAssetsDiff } export function snapshotAllocator( diff --git a/src/entities/exitRequest.ts b/src/entities/exitRequest.ts index fa99f08..3da9d93 100644 --- a/src/entities/exitRequest.ts +++ b/src/entities/exitRequest.ts @@ -1,10 +1,11 @@ import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' -import { Distributor, ExitRequest, Network, OsToken, OsTokenConfig, Vault } from '../../generated/schema' +import { ExitRequest, Network, Vault } from '../../generated/schema' import { loadV2Pool } from './v2pool' import { convertSharesToAssets, getUpdateStateCall } from './vault' -import { loadAllocator, snapshotAllocator } from './allocator' -import { loadOsTokenHolder, snapshotOsTokenHolder } from './osTokenHolder' +import { loadAllocator } from './allocator' +import { loadOsTokenHolder } from './osTokenHolder' import { chunkedVaultMulticall } from '../helpers/utils' +import { getIsOsTokenVault } from './network' const secondsInDay = '86400' const getExitQueueIndexSelector = '0x60d60e6e' @@ -15,19 +16,13 @@ export function loadExitRequest(vault: Address, positionTicket: BigInt): ExitReq return ExitRequest.load(exitRequestId) } -export function updateExitRequests( - network: Network, - osToken: OsToken, - distributor: Distributor, - vault: Vault, - osTokenConfig: OsTokenConfig, - timestamp: BigInt, -): void { +export function updateExitRequests(network: Network, vault: Vault, timestamp: BigInt): void { // If vault is in "genesis" mode, we need to wait for legacy migration if (vault.isGenesis && !loadV2Pool()!.migrated) { return } + const isOsTokenVault = getIsOsTokenVault(network, vault) const exitRequests: Array = vault.exitRequests.load() const updateStateCall: Bytes | null = getUpdateStateCall(vault) @@ -132,16 +127,24 @@ export function updateExitRequests( exitRequest.save() - snapshotExitRequest( - network, - osToken, - distributor, - vault, - osTokenConfig, - exitRequest, - exitRequest.totalAssets.minus(totalAssetsBefore), - timestamp, - ) + if (exitRequest.receiver.notEqual(exitRequest.owner)) { + continue + } + + const allocator = loadAllocator(Address.fromBytes(exitRequest.owner), Address.fromString(vault.id))! + const earnedAssets = exitRequest.totalAssets.minus(totalAssetsBefore) + allocator._periodEarnedAssets = allocator._periodEarnedAssets.plus(earnedAssets) + allocator.save() + + if (!isOsTokenVault) { + continue + } + + const osTokenHolder = loadOsTokenHolder(Address.fromBytes(exitRequest.owner)) + if (osTokenHolder) { + osTokenHolder._periodEarnedAssets = osTokenHolder._periodEarnedAssets.plus(earnedAssets) + osTokenHolder.save() + } } } @@ -162,33 +165,3 @@ function getCalculateExitedAssetsCall( .concat(ethereum.encode(ethereum.Value.fromUnsignedBigInt(timestamp))!) .concat(ethereum.encode(ethereum.Value.fromUnsignedBigInt(exitQueueIndex))!) } - -export function snapshotExitRequest( - network: Network, - osToken: OsToken, - distributor: Distributor, - vault: Vault, - osTokenConfig: OsTokenConfig, - exitRequest: ExitRequest, - earnedAssets: BigInt, - timestamp: BigInt, -): void { - if (exitRequest.receiver.notEqual(exitRequest.owner)) { - return - } - - const allocator = loadAllocator(Address.fromBytes(exitRequest.owner), Address.fromString(vault.id))! - snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, earnedAssets, timestamp) - - const osTokenVaultIds = network.osTokenVaultIds - for (let i = 0; i < osTokenVaultIds.length; i++) { - // if vault is an OsToken vault, snapshot exit request for osToken holder - if (vault.id === osTokenVaultIds[i]) { - const osTokenHolder = loadOsTokenHolder(Address.fromBytes(allocator.address)) - if (osTokenHolder) { - snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, earnedAssets, timestamp) - } - break - } - } -} diff --git a/src/entities/leverageStrategy.ts b/src/entities/leverageStrategy.ts index c1c94dd..eef36b1 100644 --- a/src/entities/leverageStrategy.ts +++ b/src/entities/leverageStrategy.ts @@ -1,7 +1,6 @@ -import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts' +import { Address, BigDecimal, BigInt, log } from '@graphprotocol/graph-ts' import { Aave, - AavePosition, Distributor, ExitRequest, LeverageStrategyPosition, @@ -14,19 +13,14 @@ import { } from '../../generated/schema' import { AaveLeverageStrategy } from '../../generated/PeriodicTasks/AaveLeverageStrategy' import { AAVE_LEVERAGE_STRATEGY, WAD } from '../helpers/constants' -import { loadAllocator, snapshotAllocator } from './allocator' +import { loadAllocator } from './allocator' import { convertAssetsToOsTokenShares, convertOsTokenSharesToAssets, getOsTokenApy } from './osToken' import { getAnnualReward } from '../helpers/utils' import { getVaultApy, getVaultOsTokenMintApy } from './vault' -import { - createOrLoadAavePosition, - getAaveBorrowApy, - getAaveSupplyApy, - loadAavePosition, - updateAavePosition, -} from './aave' +import { getAaveBorrowApy, getAaveSupplyApy, loadAavePosition } from './aave' import { convertStringToDistributionType, DistributionType, getPeriodicDistributionApy } from './merkleDistributor' -import { loadOsTokenHolder, snapshotOsTokenHolder } from './osTokenHolder' +import { loadOsTokenHolder } from './osTokenHolder' +import { getIsOsTokenVault } from './network' export function loadLeverageStrategyPosition(vault: Address, user: Address): LeverageStrategyPosition | null { const leverageStrategyPositionId = `${vault.toHex()}-${user.toHex()}` @@ -58,41 +52,13 @@ export function createOrLoadLeverageStrategyPosition(vault: Address, user: Addre return leverageStrategyPosition } -export function snapshotLeverageStrategyPosition( - network: Network, - osToken: OsToken, - distributor: Distributor, - vault: Vault, - osTokenConfig: OsTokenConfig, - position: LeverageStrategyPosition, - totalAssetsDiff: BigInt, - earnedAssetsDiff: BigInt, - timestamp: BigInt, -): void { - let userAddress = Address.fromBytes(position.user) - const allocator = loadAllocator(userAddress, Address.fromString(vault.id)) - if (allocator) { - snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, earnedAssetsDiff, timestamp) - } - - const osTokenHolder = loadOsTokenHolder(userAddress)! - snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, totalAssetsDiff, timestamp) -} - export function updateLeverageStrategyPosition(aave: Aave, osToken: OsToken, position: LeverageStrategyPosition): void { if (aave.leverageMaxBorrowLtvPercent.isZero()) { assert(false, 'Leverage max borrow LTV percent is zero') } // get and update borrow position state const proxy = Address.fromBytes(position.proxy) - let borrowState: AavePosition - let _borrowState = loadAavePosition(proxy) - if (_borrowState === null) { - borrowState = createOrLoadAavePosition(proxy) - updateAavePosition(borrowState) - } else { - borrowState = _borrowState - } + let borrowState = loadAavePosition(proxy)! const borrowedAssets = borrowState.borrowedAssets const suppliedOsTokenShares = borrowState.suppliedOsTokenShares @@ -153,45 +119,86 @@ export function updateLeverageStrategyPosition(aave: Aave, osToken: OsToken, pos position.save() } -export function updateLeverageStrategyPositions( - network: Network, - aave: Aave, +export function updatePeriodEarnedAssets( osToken: OsToken, - distributor: Distributor, vault: Vault, - osTokenConfig: OsTokenConfig, - timestamp: BigInt, + totalAssetsBefore: BigInt, + assetsBefore: BigInt, + osTokenSharesBefore: BigInt, + isOsTokenVault: boolean, + position: LeverageStrategyPosition, ): void { + const osTokenAssetsBefore = totalAssetsBefore.minus(assetsBefore) + + const assetsAfter = position.assets.plus(position.exitingAssets) + const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) + const osTokenAssetsAfter = position.totalAssets.minus(assetsAfter) + + const earnedAssets = assetsAfter.plus(osTokenAssetsAfter).minus(assetsBefore).minus(osTokenAssetsBefore) + + const userAddress = Address.fromBytes(position.user) + const allocator = loadAllocator(userAddress, Address.fromString(vault.id)) + if (allocator) { + let mintedLockedOsTokenShares: BigInt + if (osTokenSharesAfter.gt(allocator.mintedOsTokenShares)) { + mintedLockedOsTokenShares = allocator.mintedOsTokenShares + } else { + mintedLockedOsTokenShares = osTokenSharesAfter + } + let allocatorEarnedAssets = earnedAssets + const mintedLockedOsTokenAssetsBefore = mintedLockedOsTokenShares + .times(osTokenAssetsBefore) + .div(osTokenSharesBefore) + const mintedLockedOsTokenAssetsAfter = convertOsTokenSharesToAssets(osToken, mintedLockedOsTokenShares) + if (mintedLockedOsTokenAssetsAfter.lt(mintedLockedOsTokenAssetsBefore)) { + log.error( + 'updatePeriodEarnedAssets invalid minted OsToken shares: mintedLockedOsTokenAssetsAfter={} mintedLockedOsTokenAssetsBefore={} userAddress={} vault={}', + [ + mintedLockedOsTokenAssetsAfter.toString(), + mintedLockedOsTokenAssetsBefore.toString(), + userAddress.toHex(), + vault.id, + ], + ) + assert(false, 'invalid minted OsToken shares') + } + allocatorEarnedAssets = allocatorEarnedAssets.minus( + mintedLockedOsTokenAssetsAfter.minus(mintedLockedOsTokenAssetsBefore), + ) + allocator._periodEarnedAssets = allocator._periodEarnedAssets.plus(allocatorEarnedAssets) + allocator.save() + } + + if (!isOsTokenVault) { + return + } + + const osTokenHolder = loadOsTokenHolder(userAddress)! + osTokenHolder._periodEarnedAssets = osTokenHolder._periodEarnedAssets.plus(earnedAssets) + osTokenHolder.save() +} + +export function updateLeverageStrategyPositions(network: Network, aave: Aave, osToken: OsToken, vault: Vault): void { let position: LeverageStrategyPosition const leveragePositions: Array = vault.leveragePositions.load() + + let isOsTokenVault = getIsOsTokenVault(network, vault) for (let i = 0; i < leveragePositions.length; i++) { position = leveragePositions[i] - const osTokenSharesBefore = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsBefore = position.assets.plus(position.exitingAssets) const totalAssetsBefore = position.totalAssets + const assetsBefore = position.assets.plus(position.exitingAssets) + const osTokenSharesBefore = position.osTokenShares.plus(position.exitingOsTokenShares) updateLeverageStrategyPosition(aave, osToken, position) - const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsAfter = position.assets.plus(position.exitingAssets) - const totalAssetsAfter = position.totalAssets - - const assetsDiff = assetsAfter.minus(assetsBefore) - const osTokenSharesDiff = osTokenSharesAfter.minus(osTokenSharesBefore) - - const earnedAssetsDiff = convertOsTokenSharesToAssets(osToken, osTokenSharesDiff).plus(assetsDiff) - const totalAssetsDiff = totalAssetsAfter.minus(totalAssetsBefore) - - snapshotLeverageStrategyPosition( - network, + updatePeriodEarnedAssets( osToken, - distributor, vault, - osTokenConfig, + totalAssetsBefore, + assetsBefore, + osTokenSharesBefore, + isOsTokenVault, position, - totalAssetsDiff, - earnedAssetsDiff, - timestamp, ) } } diff --git a/src/entities/network.ts b/src/entities/network.ts index 88f0a9d..55da025 100644 --- a/src/entities/network.ts +++ b/src/entities/network.ts @@ -20,6 +20,7 @@ export function createOrLoadNetwork(): Network { network.vaultIds = [] network.osTokenVaultIds = [] network.oraclesConfigIpfsHash = '' + network.snapshotsCount = BigInt.zero() network.assetsUsdRate = BigDecimal.zero() network.swiseUsdRate = BigDecimal.zero() network.daiUsdRate = BigDecimal.zero() @@ -40,6 +41,16 @@ export function isGnosisNetwork(): boolean { return NETWORK == 'chiado' || NETWORK == 'gnosis' || NETWORK == 'xdai' } +export function getIsOsTokenVault(network: Network, vault: Vault): boolean { + const osTokenVaultIds = network.osTokenVaultIds + for (let i = 0; i < osTokenVaultIds.length; i++) { + if (vault.id === osTokenVaultIds[i]) { + return true + } + } + return false +} + export function createOrLoadUser(userAddress: Bytes): User { const id = userAddress.toHexString() diff --git a/src/entities/osToken.ts b/src/entities/osToken.ts index e9b0ae8..6c70066 100644 --- a/src/entities/osToken.ts +++ b/src/entities/osToken.ts @@ -24,24 +24,24 @@ export function createOrLoadOsToken(): OsToken { osToken.feePercent = 0 osToken.totalSupply = BigInt.zero() osToken.totalAssets = BigInt.zero() + osToken._periodEarnedAssets = BigInt.zero() osToken.save() } return osToken } -export function updateOsTokenTotalAssets(osToken: OsToken): BigInt { +export function updateOsTokenTotalAssets(osToken: OsToken): void { const osTokenVaultController = OsTokenVaultControllerContact.bind(OS_TOKEN_VAULT_CONTROLLER) const newTotalAssets = osTokenVaultController.totalAssets() const osTokenTotalAssetsDiff = newTotalAssets.minus(osToken.totalAssets) if (osTokenTotalAssetsDiff.lt(BigInt.zero())) { log.error('[OsToken] osTokenTotalAssetsDiff cannot be negative={}', [osTokenTotalAssetsDiff.toString()]) - return BigInt.zero() + return } osToken.totalAssets = newTotalAssets + osToken._periodEarnedAssets = osToken._periodEarnedAssets.plus(osTokenTotalAssetsDiff) osToken.save() - - return osTokenTotalAssetsDiff } export function updateOsTokenApy(osToken: OsToken, newAvgRewardPerSecond: BigInt): void { diff --git a/src/entities/osTokenHolder.ts b/src/entities/osTokenHolder.ts index deff9c8..89574a2 100644 --- a/src/entities/osTokenHolder.ts +++ b/src/entities/osTokenHolder.ts @@ -33,6 +33,7 @@ export function createOrLoadOsTokenHolder(holderAddress: Address): OsTokenHolder holder.osToken = osTokenId holder.transfersCount = BigInt.zero() holder.apy = BigDecimal.zero() + holder._periodEarnedAssets = BigInt.zero() holder.save() } @@ -141,11 +142,11 @@ export function getOsTokenHolderTotalAssets(network: Network, osToken: OsToken, return totalAssets } -export function updateOsTokenHolderAssets(osToken: OsToken, osTokenHolder: OsTokenHolder): BigInt { +export function updateOsTokenHolderAssets(osToken: OsToken, osTokenHolder: OsTokenHolder): void { const assetsBefore = osTokenHolder.assets osTokenHolder.assets = convertOsTokenSharesToAssets(osToken, osTokenHolder.balance) + osTokenHolder._periodEarnedAssets = osTokenHolder._periodEarnedAssets.plus(osTokenHolder.assets.minus(assetsBefore)) osTokenHolder.save() - return osTokenHolder.assets.minus(assetsBefore) } export function snapshotOsTokenHolder( diff --git a/src/entities/rewardSplitter.ts b/src/entities/rewardSplitter.ts index e2c3352..5db3fda 100644 --- a/src/entities/rewardSplitter.ts +++ b/src/entities/rewardSplitter.ts @@ -1,16 +1,9 @@ import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' -import { - Distributor, - OsToken, - OsTokenConfig, - RewardSplitter, - RewardSplitterShareHolder, - Vault, -} from '../../generated/schema' +import { RewardSplitter, RewardSplitterShareHolder, Vault } from '../../generated/schema' import { convertSharesToAssets } from './vault' import { loadV2Pool } from './v2pool' -import { createOrLoadAllocator, snapshotAllocator } from './allocator' import { chunkedRewardSplitterMulticall } from '../helpers/utils' +import { createOrLoadAllocator } from './allocator' const vaultUpdateStateSelector = '0x79c702ad' const syncRewardsCallSelector = '0x72c0c211' @@ -39,13 +32,7 @@ export function createOrLoadRewardSplitterShareHolder( return rewardSplitterShareHolder } -export function updateRewardSplitters( - osToken: OsToken, - distributor: Distributor, - osTokenConfig: OsTokenConfig, - vault: Vault, - timestamp: BigInt, -): void { +export function updateRewardSplitters(vault: Vault): void { if (vault.isGenesis && !loadV2Pool()!.migrated) { // wait for the migration return @@ -79,32 +66,16 @@ export function updateRewardSplitters( shareHolder.earnedVaultShares = ethereum.decode('uint256', result[j])!.toBigInt() shareHolder.earnedVaultAssets = convertSharesToAssets(vault, shareHolder.earnedVaultShares) shareHolder.save() - snapshotRewardSplitterShareHolder( - osToken, - distributor, - vault, - osTokenConfig, - shareHolder, + + const allocator = createOrLoadAllocator(Address.fromBytes(shareHolder.address), Address.fromString(vault.id)) + allocator._periodEarnedAssets = allocator._periodEarnedAssets.plus( shareHolder.earnedVaultAssets.minus(earnedVaultAssetsBefore), - timestamp, ) + allocator.save() } } } -export function snapshotRewardSplitterShareHolder( - osToken: OsToken, - distributor: Distributor, - vault: Vault, - osTokenConfig: OsTokenConfig, - shareHolder: RewardSplitterShareHolder, - earnedAssets: BigInt, - timestamp: BigInt, -): void { - const allocator = createOrLoadAllocator(Address.fromBytes(shareHolder.address), Address.fromString(vault.id)) - snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, earnedAssets, timestamp) -} - function _getVaultUpdateStateCall(vault: Vault): Bytes | null { if ( vault.rewardsRoot === null || diff --git a/src/mappings/gnoVault.ts b/src/mappings/gnoVault.ts index 31c9275..812f4e7 100644 --- a/src/mappings/gnoVault.ts +++ b/src/mappings/gnoVault.ts @@ -4,7 +4,7 @@ import { Allocator } from '../../generated/schema' import { XdaiSwapped } from '../../generated/templates/GnoVault/GnoVault' import { WAD } from '../helpers/constants' import { loadNetwork } from '../entities/network' -import { getAllocatorApy, snapshotAllocator, updateAllocatorAssets } from '../entities/allocator' +import { getAllocatorApy, updateAllocatorAssets } from '../entities/allocator' import { convertSharesToAssets, loadVault, snapshotVault, updateVaultApy } from '../entities/vault' import { createOrLoadV2Pool } from '../entities/v2pool' import { loadOsTokenConfig } from '../entities/osTokenConfig' @@ -60,8 +60,8 @@ export function handleXdaiSwapped(event: XdaiSwapped): void { allocator = allocators[j] const earnedAssets = updateAllocatorAssets(osToken, osTokenConfig, vault, allocator) allocator.apy = getAllocatorApy(osToken, osTokenConfig, vault, distributor, allocator, false) + allocator._periodEarnedAssets = allocator._periodEarnedAssets.plus(earnedAssets) allocator.save() - snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, earnedAssets, timestamp) } log.info('[GnoVault] XdaiSwapped vault={} xdai={} gno={}', [ diff --git a/src/mappings/keeper.ts b/src/mappings/keeper.ts index 3e5340d..ad444fc 100644 --- a/src/mappings/keeper.ts +++ b/src/mappings/keeper.ts @@ -1,4 +1,14 @@ -import { Address, BigInt, Bytes, DataSourceContext, ipfs, json, JSONValue, log } from '@graphprotocol/graph-ts' +import { + Address, + BigInt, + Bytes, + DataSourceContext, + ethereum, + ipfs, + json, + JSONValue, + log, +} from '@graphprotocol/graph-ts' import { BLOCKLIST_ERC20_VAULT_FACTORY_V2, BLOCKLIST_ERC20_VAULT_FACTORY_V3, @@ -26,22 +36,9 @@ import { RewardSplitterFactory as RewardSplitterFactoryTemplate, VaultFactory as VaultFactoryTemplate, } from '../../generated/templates' -import { Allocator, OsTokenHolder } from '../../generated/schema' -import { - createOrLoadOsToken, - loadOsToken, - snapshotOsToken, - updateOsTokenApy, - updateOsTokenTotalAssets, -} from '../entities/osToken' -import { - createOrLoadAllocator, - getAllocatorApy, - getAllocatorsMintedShares, - snapshotAllocator, - updateAllocatorAssets, - updateAllocatorMintedOsTokenShares, -} from '../entities/allocator' +import { Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' +import { createOrLoadOsToken, loadOsToken, snapshotOsToken, updateOsTokenApy } from '../entities/osToken' +import { createOrLoadAllocator, getAllocatorApy, snapshotAllocator, updateAllocatorAssets } from '../entities/allocator' import { createOrLoadNetwork, increaseUserVaultsCount, loadNetwork } from '../entities/network' import { ConfigUpdated, @@ -56,14 +53,15 @@ import { updateExitRequests } from '../entities/exitRequest' import { updateRewardSplitters } from '../entities/rewardSplitter' import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' -import { loadVault, updateVaultMaxBoostApy, updateVaults } from '../entities/vault' -import { getOsTokenHolderApy, snapshotOsTokenHolder, updateOsTokenHolderAssets } from '../entities/osTokenHolder' +import { loadVault, snapshotVault, updateVaultMaxBoostApy, updateVaults } from '../entities/vault' +import { getOsTokenHolderApy, snapshotOsTokenHolder } from '../entities/osTokenHolder' import { createOrLoadAave, loadAave } from '../entities/aave' import { createOrLoadDistributor, loadDistributor } from '../entities/merkleDistributor' const IS_PRIVATE_KEY = 'isPrivate' const IS_ERC20_KEY = 'isErc20' const IS_BLOCKLIST_KEY = 'isBlocklist' +const secondsInDay = 86400 export function handleOwnershipTransferred(event: OwnershipTransferred): void { createOrLoadV2Pool() @@ -232,20 +230,9 @@ export function handleRewardsUpdated(event: RewardsUpdated): void { // update OsToken const osToken = loadOsToken()! - const osTokenEarnedAssets = updateOsTokenTotalAssets(osToken) updateOsTokenApy(osToken, newAvgRewardPerSecond) - snapshotOsToken(osToken, osTokenEarnedAssets, blockTimestamp) - // update assets of all the osToken holders const network = loadNetwork()! - let osTokenHolder: OsTokenHolder - const osTokenHolderAssetsDiffs: Array = [] - const osTokenHolders: Array = osToken.holders.load() - for (let i = 0; i < osTokenHolders.length; i++) { - osTokenHolder = osTokenHolders[i] - osTokenHolderAssetsDiffs.push(updateOsTokenHolderAssets(osToken, osTokenHolder)) - } - const distributor = loadDistributor()! const vaultIds = network.vaultIds for (let i = 0; i < vaultIds.length; i++) { @@ -279,29 +266,25 @@ export function handleRewardsUpdated(event: RewardsUpdated): void { // update allocators let allocator: Allocator let allocators: Array = vault.allocators.load() - const allocatorsMintedOsTokenShares = getAllocatorsMintedShares(vault, allocators) const allocatorsAssetsDiffs: Array = [] - const mintedOsTokenAssetsDiffs: Array = [] for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] allocatorsAssetsDiffs.push(updateAllocatorAssets(osToken, osTokenConfig, vault, allocator)) - mintedOsTokenAssetsDiffs.push( - updateAllocatorMintedOsTokenShares(osToken, osTokenConfig, allocator, allocatorsMintedOsTokenShares[j]), - ) } // update exit requests - updateExitRequests(network, osToken, distributor, vault, osTokenConfig, updateTimestamp) + updateExitRequests(network, vault, updateTimestamp) // update reward splitters - updateRewardSplitters(osToken, distributor, osTokenConfig, vault, updateTimestamp) + updateRewardSplitters(vault) // update OsToken exit requests updateOsTokenExitRequests(osToken, vault) // update leverage strategy positions - updateLeverageStrategyPositions(network, aave, osToken, distributor, vault, osTokenConfig, blockTimestamp) + updateLeverageStrategyPositions(network, aave, osToken, vault) + // update allocators apys for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] allocator.apy = getAllocatorApy(osToken, osTokenConfig, vault, distributor, allocator, false) @@ -315,27 +298,19 @@ export function handleRewardsUpdated(event: RewardsUpdated): void { allocatorsAssetsDiffs[j], updateTimestamp, ) - snapshotAllocator( - osToken, - osTokenConfig, - vault, - distributor, - allocator, - mintedOsTokenAssetsDiffs[j].neg(), - blockTimestamp, - ) } // update vault max boost apys updateVaultMaxBoostApy(aave, osToken, vault, osTokenConfig, distributor, blockNumber) } - // update assets of all the osToken holders + // update osToken holders apys + let osTokenHolder: OsTokenHolder + const osTokenHolders: Array = osToken.holders.load() for (let i = 0; i < osTokenHolders.length; i++) { osTokenHolder = osTokenHolders[i] osTokenHolder.apy = getOsTokenHolderApy(network, osToken, distributor, osTokenHolder, false) osTokenHolder.save() - snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, osTokenHolderAssetsDiffs[i], blockTimestamp) } log.info('[Keeper] RewardsUpdated rewardsRoot={} rewardsIpfsHash={} updateTimestamp={} blockTimestamp={}', [ @@ -404,3 +379,54 @@ export function handleConfigUpdated(event: ConfigUpdated): void { network.save() log.info('[Keeper] ConfigUpdated configIpfsHash={}', [configIpfsHash]) } + +export function handleDailySnapshots(block: ethereum.Block): void { + const network = loadNetwork() + if (!network) { + return + } + + const timestamp = block.timestamp + const newSnapshotsCount = timestamp.plus(BigInt.fromI32(30)).div(BigInt.fromI32(secondsInDay)) + if (newSnapshotsCount.le(network.snapshotsCount)) { + return + } + network.snapshotsCount = newSnapshotsCount + network.save() + + const osToken = loadOsToken() + if (!osToken) { + return + } + snapshotOsToken(osToken, osToken._periodEarnedAssets, timestamp) + osToken._periodEarnedAssets = BigInt.zero() + osToken.save() + + let osTokenHolder: OsTokenHolder + const distributor = loadDistributor()! + const osTokenHolders: Array = osToken.holders.load() + for (let i = 0; i < osTokenHolders.length; i++) { + osTokenHolder = osTokenHolders[i] + snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, osTokenHolder._periodEarnedAssets, timestamp) + osTokenHolder._periodEarnedAssets = BigInt.zero() + osTokenHolder.save() + } + + let vault: Vault + let osTokenConfig: OsTokenConfig + const vaultIds = network.vaultIds + for (let i = 0; i < vaultIds.length; i++) { + vault = loadVault(Address.fromString(vaultIds[i]))! + osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! + snapshotVault(vault, BigInt.zero(), timestamp) + + const allocators: Array = vault.allocators.load() + for (let j = 0; j < allocators.length; j++) { + const allocator = allocators[j] + snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, allocator._periodEarnedAssets, timestamp) + allocator._periodEarnedAssets = BigInt.zero() + allocator.save() + } + } + log.info('[DailySnapshots] block={} timestamp={}', [block.number.toString(), timestamp.toString()]) +} diff --git a/src/mappings/leverageStrategy.ts b/src/mappings/leverageStrategy.ts index 2dc924a..8a19678 100644 --- a/src/mappings/leverageStrategy.ts +++ b/src/mappings/leverageStrategy.ts @@ -10,8 +10,8 @@ import { createTransaction } from '../entities/transaction' import { createOrLoadLeverageStrategyPosition, loadLeverageStrategyPosition, - snapshotLeverageStrategyPosition, updateLeverageStrategyPosition, + updatePeriodEarnedAssets, } from '../entities/leverageStrategy' import { convertOsTokenSharesToAssets, loadOsToken } from '../entities/osToken' import { @@ -22,11 +22,12 @@ import { loadAllocator, } from '../entities/allocator' import { getOsTokenHolderApy, loadOsTokenHolder } from '../entities/osTokenHolder' -import { loadNetwork } from '../entities/network' +import { getIsOsTokenVault, loadNetwork } from '../entities/network' import { loadVault } from '../entities/vault' import { loadOsTokenConfig } from '../entities/osTokenConfig' -import { createOrLoadAavePosition, loadAave } from '../entities/aave' +import { createOrLoadAavePosition, loadAave, loadAavePosition, updateAavePosition } from '../entities/aave' import { loadDistributor } from '../entities/merkleDistributor' +import { WAD } from '../helpers/constants' function _updateAllocatorAndOsTokenHolderApys( network: Network, @@ -69,7 +70,6 @@ export function handleDeposited(event: Deposited): void { const vaultAddress = event.params.vault const userAddress = event.params.user const depositedOsTokenShares = event.params.osTokenShares - const timestamp = event.block.timestamp const aave = loadAave()! const network = loadNetwork()! @@ -94,31 +94,19 @@ export function handleDeposited(event: Deposited): void { const assetsBefore = position.assets.plus(position.exitingAssets) const totalAssetsBefore = position.totalAssets + updateAavePosition(createOrLoadAavePosition(Address.fromBytes(position.proxy))) updateLeverageStrategyPosition(aave, osToken, position) - - const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsAfter = position.assets.plus(position.exitingAssets) - const totalAssetsAfter = position.totalAssets - - const assetsDiff = assetsAfter.minus(assetsBefore) - const osTokenSharesDiff = osTokenSharesAfter.minus(osTokenSharesBefore).minus(depositedOsTokenShares) - - const earnedAssetsDiff = convertOsTokenSharesToAssets(osToken, osTokenSharesDiff).plus(assetsDiff) - const totalAssetsDiff = totalAssetsAfter.minus(totalAssetsBefore) - _updateAllocatorAndOsTokenHolderApys(network, osToken, osTokenConfig, distributor, vault, userAddress) - if (!ignoreSnapshot) { - snapshotLeverageStrategyPosition( - network, + if (!ignoreSnapshot && osTokenSharesBefore.gt(BigInt.zero())) { + updatePeriodEarnedAssets( osToken, - distributor, vault, - osTokenConfig, + totalAssetsBefore.plus(convertOsTokenSharesToAssets(osToken, depositedOsTokenShares)), + assetsBefore, + osTokenSharesBefore.plus(depositedOsTokenShares), + getIsOsTokenVault(network, vault), position, - totalAssetsDiff, - earnedAssetsDiff, - timestamp, ) } @@ -146,7 +134,6 @@ export function handleExitQueueEntered(event: ExitQueueEntered): void { const userAddress = event.params.user const positionTicket = event.params.positionTicket const exitingPercent = event.params.positionPercent - const timestamp = event.block.timestamp const aave = loadAave()! const osToken = loadOsToken()! @@ -174,30 +161,17 @@ export function handleExitQueueEntered(event: ExitQueueEntered): void { position.exitingPercent = exitingPercent updateLeverageStrategyPosition(aave, osToken, position) - - const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsAfter = position.assets.plus(position.exitingAssets) - const totalAssetsAfter = position.totalAssets - - const assetsDiff = assetsAfter.minus(assetsBefore) - const osTokenSharesDiff = osTokenSharesAfter.minus(osTokenSharesBefore) - - const earnedAssetsDiff = convertOsTokenSharesToAssets(osToken, osTokenSharesDiff).plus(assetsDiff) - const totalAssetsDiff = totalAssetsAfter.minus(totalAssetsBefore) - _updateAllocatorAndOsTokenHolderApys(network, osToken, osTokenConfig, distributor, vault, userAddress) if (!ignoreSnapshot) { - snapshotLeverageStrategyPosition( - network, + updatePeriodEarnedAssets( osToken, - distributor, vault, - osTokenConfig, + totalAssetsBefore, + assetsBefore, + osTokenSharesBefore, + getIsOsTokenVault(network, vault), position, - totalAssetsDiff, - earnedAssetsDiff, - timestamp, ) } @@ -212,8 +186,6 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { const vaultAddress = event.params.vault const userAddress = event.params.user const claimedOsTokenShares = event.params.osTokenShares - const claimedAssets = event.params.assets - const timestamp = event.block.timestamp const osToken = loadOsToken()! const network = loadNetwork()! @@ -234,38 +206,28 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { ]) } + const osTokenSharesBefore = position.osTokenShares + const assetsBefore = position.assets + const totalAssetsBefore = position.totalAssets.minus( + position.totalAssets.times(position.exitingPercent).div(BigInt.fromString(WAD)), + ) + position.exitRequest = null position.exitingPercent = BigInt.zero() - const osTokenSharesBefore = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsBefore = position.assets.plus(position.exitingAssets) - const totalAssetsBefore = position.totalAssets - + updateAavePosition(loadAavePosition(Address.fromBytes(position.proxy))!) updateLeverageStrategyPosition(aave, osToken, position) - - const osTokenSharesAfter = position.osTokenShares.plus(position.exitingOsTokenShares) - const assetsAfter = position.assets.plus(position.exitingAssets) - const totalAssetsAfter = position.totalAssets - - const assetsDiff = assetsAfter.plus(claimedAssets).minus(assetsBefore) - const osTokenSharesDiff = osTokenSharesAfter.plus(claimedOsTokenShares).minus(osTokenSharesBefore) - - const earnedAssetsDiff = convertOsTokenSharesToAssets(osToken, osTokenSharesDiff).plus(assetsDiff) - const totalAssetsDiff = totalAssetsAfter.minus(totalAssetsBefore) - _updateAllocatorAndOsTokenHolderApys(network, osToken, osTokenConfig, distributor, vault, userAddress) if (!ignoreSnapshot) { - snapshotLeverageStrategyPosition( - network, + updatePeriodEarnedAssets( osToken, - distributor, vault, - osTokenConfig, + totalAssetsBefore, + assetsBefore, + osTokenSharesBefore, + getIsOsTokenVault(network, vault), position, - totalAssetsDiff, - earnedAssetsDiff, - timestamp, ) } diff --git a/src/mappings/periodicTasks.ts b/src/mappings/periodicTasks.ts index 17f3113..17d88d3 100644 --- a/src/mappings/periodicTasks.ts +++ b/src/mappings/periodicTasks.ts @@ -1,16 +1,11 @@ -import { Address, BigInt, ethereum, log } from '@graphprotocol/graph-ts' -import { loadVault, snapshotVault, updateVaultMaxBoostApy } from '../entities/vault' -import { loadOsToken, snapshotOsToken, updateOsTokenTotalAssets } from '../entities/osToken' +import { Address, ethereum, log } from '@graphprotocol/graph-ts' +import { loadVault, updateVaultMaxBoostApy } from '../entities/vault' +import { loadOsToken, updateOsTokenTotalAssets } from '../entities/osToken' import { loadNetwork } from '../entities/network' import { Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' -import { - getAllocatorApy, - getAllocatorsMintedShares, - snapshotAllocator, - updateAllocatorMintedOsTokenShares, -} from '../entities/allocator' +import { getAllocatorApy, getAllocatorsMintedShares, updateAllocatorMintedOsTokenShares } from '../entities/allocator' import { updateExitRequests } from '../entities/exitRequest' -import { getOsTokenHolderApy, snapshotOsTokenHolder, updateOsTokenHolderAssets } from '../entities/osTokenHolder' +import { getOsTokenHolderApy, updateOsTokenHolderAssets } from '../entities/osTokenHolder' import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { loadOsTokenConfig } from '../entities/osTokenConfig' @@ -33,16 +28,14 @@ export function handlePeriodicTasks(block: ethereum.Block): void { // update osToken const osToken = loadOsToken()! - const osTokenAssetsDiff = updateOsTokenTotalAssets(osToken) - snapshotOsToken(osToken, osTokenAssetsDiff, timestamp) + updateOsTokenTotalAssets(osToken) // update assets of all the osToken holders let osTokenHolder: OsTokenHolder - const osTokenHolderAssetsDiffs: Array = [] const osTokenHolders: Array = osToken.holders.load() for (let i = 0; i < osTokenHolders.length; i++) { osTokenHolder = osTokenHolders[i] - osTokenHolderAssetsDiffs.push(updateOsTokenHolderAssets(osToken, osTokenHolder)) + updateOsTokenHolderAssets(osToken, osTokenHolder) } // update distributions @@ -63,43 +56,29 @@ export function handlePeriodicTasks(block: ethereum.Block): void { let allocator: Allocator let allocators: Array = vault.allocators.load() const allocatorsMintedOsTokenShares = getAllocatorsMintedShares(vault, allocators) - let mintedOsTokenAssetsDiff: Array = [] for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] - mintedOsTokenAssetsDiff.push( - updateAllocatorMintedOsTokenShares(osToken, osTokenConfig, allocator, allocatorsMintedOsTokenShares[j]), - ) + updateAllocatorMintedOsTokenShares(osToken, osTokenConfig, allocator, allocatorsMintedOsTokenShares[j]) } // update exit requests - updateExitRequests(network, osToken, distributor, vault, osTokenConfig, timestamp) + updateExitRequests(network, vault, timestamp) // update OsToken exit requests updateOsTokenExitRequests(osToken, vault) // update leverage strategy positions - updateLeverageStrategyPositions(network, aave, osToken, distributor, vault, osTokenConfig, timestamp) + updateLeverageStrategyPositions(network, aave, osToken, vault) + // update allocators apys for (let j = 0; j < allocators.length; j++) { allocator = allocators[j] allocator.apy = getAllocatorApy(osToken, osTokenConfig, vault, distributor, allocator, false) allocator.save() - snapshotAllocator( - osToken, - osTokenConfig, - vault, - distributor, - allocator, - mintedOsTokenAssetsDiff[j].neg(), - timestamp, - ) } // update vault max boost apys updateVaultMaxBoostApy(aave, osToken, vault, osTokenConfig, distributor, blockNumber) - - // snapshot vault - snapshotVault(vault, BigInt.zero(), timestamp) } // update osToken holders apys @@ -107,7 +86,6 @@ export function handlePeriodicTasks(block: ethereum.Block): void { osTokenHolder = osTokenHolders[i] osTokenHolder.apy = getOsTokenHolderApy(network, osToken, distributor, osTokenHolder, false) osTokenHolder.save() - snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, osTokenHolderAssetsDiffs[i], timestamp) } log.info('[PeriodicTasks] block={} timestamp={}', [blockNumber.toString(), timestamp.toString()]) } diff --git a/src/mappings/rewardSplitter.ts b/src/mappings/rewardSplitter.ts index 95bf2d7..05fbaad 100644 --- a/src/mappings/rewardSplitter.ts +++ b/src/mappings/rewardSplitter.ts @@ -8,11 +8,8 @@ import { import { RewardSplitterCreated } from '../../generated/templates/RewardSplitterFactory/RewardSplitterFactory' import { RewardSplitter } from '../../generated/schema' import { createTransaction } from '../entities/transaction' -import { createOrLoadRewardSplitterShareHolder, snapshotRewardSplitterShareHolder } from '../entities/rewardSplitter' +import { createOrLoadRewardSplitterShareHolder } from '../entities/rewardSplitter' import { convertSharesToAssets, loadVault } from '../entities/vault' -import { loadOsToken } from '../entities/osToken' -import { loadDistributor } from '../entities/merkleDistributor' -import { loadOsTokenConfig } from '../entities/osTokenConfig' // Event emitted on RewardSplitter contract creation export function handleRewardSplitterCreated(event: RewardSplitterCreated): void { @@ -101,9 +98,6 @@ export function handleRewardsWithdrawn(event: RewardsWithdrawn): void { const rewardSplitter = RewardSplitter.load(rewardSplitterAddressHex)! const vault = loadVault(Address.fromString(rewardSplitter.vault))! - const osToken = loadOsToken()! - const distributor = loadDistributor()! - const osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! const shareHolder = createOrLoadRewardSplitterShareHolder(account, rewardSplitterAddress, rewardSplitter.vault) shareHolder.earnedVaultShares = shareHolder.earnedVaultShares.minus(withdrawnVaultShares) @@ -112,15 +106,6 @@ export function handleRewardsWithdrawn(event: RewardsWithdrawn): void { } shareHolder.earnedVaultAssets = convertSharesToAssets(vault, shareHolder.earnedVaultShares) shareHolder.save() - snapshotRewardSplitterShareHolder( - osToken, - distributor, - vault, - osTokenConfig, - shareHolder, - BigInt.zero(), - event.block.timestamp, - ) const txHash = event.transaction.hash.toHex() createTransaction(txHash) diff --git a/src/mappings/vault.ts b/src/mappings/vault.ts index 41f61e0..eadbe7e 100644 --- a/src/mappings/vault.ts +++ b/src/mappings/vault.ts @@ -43,7 +43,7 @@ import { decreaseUserVaultsCount, increaseUserVaultsCount, isGnosisNetwork, load import { convertOsTokenSharesToAssets, loadOsToken } from '../entities/osToken' import { DEPOSIT_DATA_REGISTRY, OS_TOKEN_CONFIG_V2_START_BLOCK, WAD } from '../helpers/constants' import { createOrLoadOsTokenConfig, loadOsTokenConfig } from '../entities/osTokenConfig' -import { loadExitRequest, snapshotExitRequest, updateExitRequests } from '../entities/exitRequest' +import { loadExitRequest, updateExitRequests } from '../entities/exitRequest' import { convertSharesToAssets, loadVault } from '../entities/vault' import { loadDistributor } from '../entities/merkleDistributor' @@ -198,14 +198,7 @@ export function handleInitialized(event: Initialized): void { if (newVersion.equals(BigInt.fromI32(3))) { // update exit requests - updateExitRequests( - loadNetwork()!, - loadOsToken()!, - loadDistributor()!, - vault, - loadOsTokenConfig(vault.osTokenConfig)!, - timestamp, - ) + updateExitRequests(loadNetwork()!, vault, timestamp) } createTransaction(event.transaction.hash.toHex()) @@ -459,13 +452,6 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { const claimedAssets = params.withdrawnAssets const vaultAddress = event.address const vaultAddressHex = vaultAddress.toHex() - const timestamp = event.block.timestamp - - const network = loadNetwork()! - const vault = loadVault(vaultAddress)! - const osToken = loadOsToken()! - const osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! - const distributor = loadDistributor()! createAllocatorAction(event, event.address, AllocatorActionType.ExitedAssetsClaimed, receiver, claimedAssets, null) @@ -511,7 +497,6 @@ export function handleExitedAssetsClaimed(event: ExitedAssetsClaimed): void { prevExitRequest.isClaimable = false prevExitRequest.isClaimed = true prevExitRequest.save() - snapshotExitRequest(network, osToken, distributor, vault, osTokenConfig, prevExitRequest, BigInt.zero(), timestamp) log.info('[Vault] ExitedAssetsClaimed vault={} prevPositionTicket={} newPositionTicket={} claimedAssets={}', [ vaultAddressHex, diff --git a/src/schema.graphql b/src/schema.graphql index 88bbee3..c700976 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -38,6 +38,9 @@ type Allocator @entity { "The exit requests of the allocator" exitRequests: [ExitRequest!]! @derivedFrom(field: "allocator") + + "The period earned assets" + _periodEarnedAssets: BigInt! } """ @@ -84,6 +87,9 @@ type OsTokenHolder @entity { "The total number of OsToken transfers" transfersCount: BigInt! + + "The period earned assets" + _periodEarnedAssets: BigInt! } """ @@ -417,6 +423,9 @@ type OsToken @entity { "The OsToken holders" holders: [OsTokenHolder!]! @derivedFrom(field: "osToken") + + "The period earned assets" + _periodEarnedAssets: BigInt! } """ @@ -479,6 +488,9 @@ type Network @entity { "The total number of non repeated vault allocators and osToken holders" usersCount: Int! + + "The total number of snapshots" + snapshotsCount: BigInt! } type User @entity { diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 4e522f0..1b27ffa 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -188,6 +188,8 @@ dataSources: handler: handleValidatorsApproval - event: ConfigUpdated(string) handler: handleConfigUpdated + blockHandlers: + - handler: handleDailySnapshots - kind: ethereum/contract name: OsTokenVaultController network: {{ network }} From adfacbaea16ea2d4b4d7bba58ba23c1205e8e5ce Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sat, 4 Jan 2025 10:38:13 +0200 Subject: [PATCH 06/17] Add missing Multicall ABI --- src/subgraph.template.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 1b27ffa..85895cf 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -78,6 +78,8 @@ dataSources: abis: - name: PriceFeed file: ./abis/PriceFeed.json + - name: Multicall + file: ./abis/Multicall.json blockHandlers: - handler: handleExchangeRates filter: From 3a9f2caae7ddfee9a2815bec5045b39955d1d34a Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sun, 5 Jan 2025 11:46:44 +0200 Subject: [PATCH 07/17] Fix multicall, remove exchange rate snapshots --- src/entities/exchangeRates.ts | 117 +++++++++++++++-------------- src/entities/osTokenVaultEscrow.ts | 9 +-- src/mappings/exchangeRates.ts | 5 +- src/schema.graphql | 36 --------- src/subgraph.template.yaml | 2 - 5 files changed, 66 insertions(+), 103 deletions(-) diff --git a/src/entities/exchangeRates.ts b/src/entities/exchangeRates.ts index a165dfa..28ac50d 100644 --- a/src/entities/exchangeRates.ts +++ b/src/entities/exchangeRates.ts @@ -1,4 +1,4 @@ -import { ExchangeRateSnapshot, Network, UniswapPool } from '../../generated/schema' +import { Network, UniswapPool } from '../../generated/schema' import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { ASSETS_USD_PRICE_FEED, @@ -9,14 +9,20 @@ import { GBP_USD_PRICE_FEED, JPY_USD_PRICE_FEED, KRW_USD_PRICE_FEED, + NETWORK, SWISE_ASSET_UNI_POOL, USDC_USD_PRICE_FEED, } from '../helpers/constants' import { chunkedMulticall } from '../helpers/utils' +import { isGnosisNetwork } from './network' const latestAnswerSelector = '0x50d25bcd' -export function updateExchangeRates(network: Network, timestamp: BigInt): void { +export function updateExchangeRates(network: Network): void { + if (NETWORK == 'chiado' || NETWORK == 'holesky') { + return + } + const latestAnswerCall = Bytes.fromHexString(latestAnswerSelector) const decimals = BigDecimal.fromString('100000000') @@ -31,17 +37,23 @@ export function updateExchangeRates(network: Network, timestamp: BigInt): void { let usdcUsdRate = BigDecimal.zero() let swiseUsdRate = BigDecimal.zero() - const contractAddresses: Array
= [ - Address.fromString(ASSETS_USD_PRICE_FEED), - Address.fromString(EUR_USD_PRICE_FEED), - Address.fromString(GBP_USD_PRICE_FEED), - Address.fromString(CNY_USD_PRICE_FEED), - Address.fromString(JPY_USD_PRICE_FEED), - Address.fromString(KRW_USD_PRICE_FEED), - Address.fromString(AUD_USD_PRICE_FEED), - Address.fromString(DAI_USD_PRICE_FEED), - Address.fromString(USDC_USD_PRICE_FEED), - ] + let contractAddresses: Array
+ const isGnosis = isGnosisNetwork() + if (isGnosis) { + contractAddresses = [Address.fromString(ASSETS_USD_PRICE_FEED)] + } else { + contractAddresses = [ + Address.fromString(ASSETS_USD_PRICE_FEED), + Address.fromString(EUR_USD_PRICE_FEED), + Address.fromString(GBP_USD_PRICE_FEED), + Address.fromString(CNY_USD_PRICE_FEED), + Address.fromString(JPY_USD_PRICE_FEED), + Address.fromString(KRW_USD_PRICE_FEED), + Address.fromString(AUD_USD_PRICE_FEED), + Address.fromString(DAI_USD_PRICE_FEED), + Address.fromString(USDC_USD_PRICE_FEED), + ] + } const contractCalls: Array = [] for (let i = 0; i < contractAddresses.length; i++) { contractCalls.push(latestAnswerCall) @@ -53,37 +65,40 @@ export function updateExchangeRates(network: Network, timestamp: BigInt): void { decodedValue = ethereum.decode('int256', response[0]!)!.toBigInt() assetsUsdRate = decodedValue.toBigDecimal().div(decimals) } - if (response[1] !== null) { - decodedValue = ethereum.decode('int256', response[1]!)!.toBigInt() - eurToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[2] !== null) { - decodedValue = ethereum.decode('int256', response[2]!)!.toBigInt() - gbpToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[3] !== null) { - decodedValue = ethereum.decode('int256', response[3]!)!.toBigInt() - cnyToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[4] !== null) { - decodedValue = ethereum.decode('int256', response[4]!)!.toBigInt() - jpyToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[5] !== null) { - decodedValue = ethereum.decode('int256', response[5]!)!.toBigInt() - krwToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[6] !== null) { - decodedValue = ethereum.decode('int256', response[6]!)!.toBigInt() - audToUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[7] !== null) { - decodedValue = ethereum.decode('int256', response[7]!)!.toBigInt() - daiUsdRate = decodedValue.toBigDecimal().div(decimals) - } - if (response[8] !== null) { - decodedValue = ethereum.decode('int256', response[8]!)!.toBigInt() - usdcUsdRate = decodedValue.toBigDecimal().div(decimals) + + if (!isGnosis) { + if (response[1] !== null) { + decodedValue = ethereum.decode('int256', response[1]!)!.toBigInt() + eurToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[2] !== null) { + decodedValue = ethereum.decode('int256', response[2]!)!.toBigInt() + gbpToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[3] !== null) { + decodedValue = ethereum.decode('int256', response[3]!)!.toBigInt() + cnyToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[4] !== null) { + decodedValue = ethereum.decode('int256', response[4]!)!.toBigInt() + jpyToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[5] !== null) { + decodedValue = ethereum.decode('int256', response[5]!)!.toBigInt() + krwToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[6] !== null) { + decodedValue = ethereum.decode('int256', response[6]!)!.toBigInt() + audToUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[7] !== null) { + decodedValue = ethereum.decode('int256', response[7]!)!.toBigInt() + daiUsdRate = decodedValue.toBigDecimal().div(decimals) + } + if (response[8] !== null) { + decodedValue = ethereum.decode('int256', response[8]!)!.toBigInt() + usdcUsdRate = decodedValue.toBigDecimal().div(decimals) + } } const swiseAssetUniPool = Address.fromString(SWISE_ASSET_UNI_POOL) @@ -115,18 +130,4 @@ export function updateExchangeRates(network: Network, timestamp: BigInt): void { network.daiUsdRate = daiUsdRate network.usdcUsdRate = usdcUsdRate network.save() - - const exchangeRateSnapshot = new ExchangeRateSnapshot(timestamp.toString()) - exchangeRateSnapshot.timestamp = timestamp.toI64() - exchangeRateSnapshot.assetsUsdRate = assetsUsdRate - exchangeRateSnapshot.swiseUsdRate = swiseUsdRate - exchangeRateSnapshot.daiUsdRate = daiUsdRate - exchangeRateSnapshot.usdcUsdRate = usdcUsdRate - exchangeRateSnapshot.usdToEurRate = usdToEurRate - exchangeRateSnapshot.usdToGbpRate = usdToGbpRate - exchangeRateSnapshot.usdToCnyRate = usdToCnyRate - exchangeRateSnapshot.usdToJpyRate = usdToJpyRate - exchangeRateSnapshot.usdToKrwRate = usdToKrwRate - exchangeRateSnapshot.usdToAudRate = usdToAudRate - exchangeRateSnapshot.save() } diff --git a/src/entities/osTokenVaultEscrow.ts b/src/entities/osTokenVaultEscrow.ts index f76cd26..8456cd5 100644 --- a/src/entities/osTokenVaultEscrow.ts +++ b/src/entities/osTokenVaultEscrow.ts @@ -63,11 +63,13 @@ export function updateOsTokenExitRequests(osToken: OsToken, vault: Vault): void let osTokenExitRequest: OsTokenExitRequest const osTokenExitRequests: Array = vault.osTokenExitRequests.load() + const unprocessedExitRequests: Array = [] for (let i = 0; i < osTokenExitRequests.length; i++) { osTokenExitRequest = osTokenExitRequests[i] if (osTokenExitRequest.osTokenShares.isZero()) { continue } + unprocessedExitRequests.push(osTokenExitRequest) contractAddresses.push(OS_TOKEN_VAULT_ESCROW) contractCalls.push(_getPositionCall(vaultAddress, osTokenExitRequest.positionTicket)) } @@ -78,11 +80,8 @@ export function updateOsTokenExitRequests(osToken: OsToken, vault: Vault): void } // process result - for (let i = 0; i < osTokenExitRequests.length; i++) { - osTokenExitRequest = osTokenExitRequests[i] - if (osTokenExitRequest.osTokenShares.isZero()) { - continue - } + for (let i = 0; i < unprocessedExitRequests.length; i++) { + osTokenExitRequest = unprocessedExitRequests[i] let decodedResult = ethereum.decode('(address,uint256,uint256)', result[i]!)!.toTuple() osTokenExitRequest.osTokenShares = decodedResult[2].toBigInt() osTokenExitRequest.ltv = getExitRequestLtv(osTokenExitRequest, osToken) diff --git a/src/mappings/exchangeRates.ts b/src/mappings/exchangeRates.ts index c115e19..e2f0042 100644 --- a/src/mappings/exchangeRates.ts +++ b/src/mappings/exchangeRates.ts @@ -4,10 +4,10 @@ import { updateExchangeRates } from '../entities/exchangeRates' export function handleExchangeRates(block: ethereum.Block): void { const network = loadNetwork()! - updateExchangeRates(network, block.timestamp) + updateExchangeRates(network) log.info( - '[ExchangeRates] assetsUsdRate={} usdToEurRate={} usdToGbpRate={} usdToCnyRate={} usdToJpyRate={} usdToKrwRate={} usdToAudRate={} daiUsdRate={} usdcUsdRate={} swiseUsdRate={}', + '[ExchangeRates] assetsUsdRate={} usdToEurRate={} usdToGbpRate={} usdToCnyRate={} usdToJpyRate={} usdToKrwRate={} usdToAudRate={} daiUsdRate={} usdcUsdRate={} swiseUsdRate={} timestamp={}', [ network.assetsUsdRate.toString(), network.usdToEurRate.toString(), @@ -19,6 +19,7 @@ export function handleExchangeRates(block: ethereum.Block): void { network.daiUsdRate.toString(), network.usdcUsdRate.toString(), network.swiseUsdRate.toString(), + block.timestamp.toString(), ], ) } diff --git a/src/schema.graphql b/src/schema.graphql index c700976..c7cc773 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -883,42 +883,6 @@ type VaultStats @aggregation(intervals: ["day"], source: "VaultSnapshot") { apy: BigDecimal! @aggregate(fn: "last", arg: "apy") } -""" -The snapshot of the exchange rates -""" -type ExchangeRateSnapshot @entity(timeseries: true) { - id: Int8! - timestamp: Timestamp! - assetsUsdRate: BigDecimal! - swiseUsdRate: BigDecimal! - daiUsdRate: BigDecimal! - usdcUsdRate: BigDecimal! - usdToEurRate: BigDecimal! - usdToGbpRate: BigDecimal! - usdToCnyRate: BigDecimal! - usdToJpyRate: BigDecimal! - usdToKrwRate: BigDecimal! - usdToAudRate: BigDecimal! -} - -""" -The aggregation of the ExchangeRate snapshots -""" -type ExchangeRateStats @aggregation(intervals: ["day"], source: "ExchangeRateSnapshot") { - id: Int8! - timestamp: Timestamp! - assetsUsdRate: BigDecimal! @aggregate(fn: "last", arg: "assetsUsdRate") - swiseUsdRate: BigDecimal! @aggregate(fn: "last", arg: "swiseUsdRate") - daiUsdRate: BigDecimal! @aggregate(fn: "last", arg: "daiUsdRate") - usdcUsdRate: BigDecimal! @aggregate(fn: "last", arg: "usdcUsdRate") - usdToEurRate: BigDecimal! @aggregate(fn: "last", arg: "usdToEurRate") - usdToGbpRate: BigDecimal! @aggregate(fn: "last", arg: "usdToGbpRate") - usdToCnyRate: BigDecimal! @aggregate(fn: "last", arg: "usdToCnyRate") - usdToJpyRate: BigDecimal! @aggregate(fn: "last", arg: "usdToJpyRate") - usdToKrwRate: BigDecimal! @aggregate(fn: "last", arg: "usdToKrwRate") - usdToAudRate: BigDecimal! @aggregate(fn: "last", arg: "usdToAudRate") -} - "Uniswap V3 pool data" type UniswapPool @entity { "The address of the pool" diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 85895cf..b821c0a 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -73,8 +73,6 @@ dataSources: file: ./mappings/exchangeRates.ts entities: - Network - - ExchangeRateSnapshot - - ExchangeRateStats abis: - name: PriceFeed file: ./abis/PriceFeed.json From b1d4357a37944ad8f82332591dedbe10b04b9e44 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sun, 5 Jan 2025 15:19:16 +0200 Subject: [PATCH 08/17] Add pruning --- src/config/chiado.json | 1 + src/config/gnosis.json | 1 + src/config/holesky.json | 1 + src/config/mainnet.json | 1 + src/subgraph.template.yaml | 2 ++ 5 files changed, 6 insertions(+) diff --git a/src/config/chiado.json b/src/config/chiado.json index 9df61e1..86fbc9f 100644 --- a/src/config/chiado.json +++ b/src/config/chiado.json @@ -1,6 +1,7 @@ { "network": "chiado", "blocksInHour": "700", + "maxRetainBlocks": "504000", "wad": "1000000000000000000", "zeroAddress": "0x0000000000000000000000000000000000000000", "v2PoolFeePercent": "1500", diff --git a/src/config/gnosis.json b/src/config/gnosis.json index 3822495..b737ad3 100644 --- a/src/config/gnosis.json +++ b/src/config/gnosis.json @@ -1,6 +1,7 @@ { "network": "gnosis", "blocksInHour": "700", + "maxRetainBlocks": "504000", "wad": "1000000000000000000", "zeroAddress": "0x0000000000000000000000000000000000000000", "v2PoolFeePercent": "1500", diff --git a/src/config/holesky.json b/src/config/holesky.json index 7f4a4ab..8ecb6cd 100644 --- a/src/config/holesky.json +++ b/src/config/holesky.json @@ -1,6 +1,7 @@ { "network": "holesky", "blocksInHour": "300", + "maxRetainBlocks": "216000", "wad": "1000000000000000000", "zeroAddress": "0x0000000000000000000000000000000000000000", "v2PoolFeePercent": "1000", diff --git a/src/config/mainnet.json b/src/config/mainnet.json index 63f51e4..60f69b5 100644 --- a/src/config/mainnet.json +++ b/src/config/mainnet.json @@ -1,6 +1,7 @@ { "network": "mainnet", "blocksInHour": "300", + "maxRetainBlocks": "216000", "wad": "1000000000000000000", "zeroAddress": "0x0000000000000000000000000000000000000000", "v2PoolFeePercent": "1000", diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index b821c0a..7643a06 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -5,6 +5,8 @@ schema: file: ./schema.graphql features: - ipfsOnEthereumContracts +indexerHints: + prune: {{ maxRetainBlocks }} dataSources: - kind: ethereum/contract name: PeriodicTasks From d14333c9f22b7a1860d96ce663ff008e2ba6cfc3 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sun, 5 Jan 2025 16:17:52 +0200 Subject: [PATCH 09/17] Revert Exchange rate snapshots removal, add pruning --- src/entities/exchangeRates.ts | 18 ++++++++++++++++-- src/entities/vault.ts | 6 +----- src/mappings/exchangeRates.ts | 2 +- src/schema.graphql | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/entities/exchangeRates.ts b/src/entities/exchangeRates.ts index 28ac50d..b2246f7 100644 --- a/src/entities/exchangeRates.ts +++ b/src/entities/exchangeRates.ts @@ -1,4 +1,4 @@ -import { Network, UniswapPool } from '../../generated/schema' +import { ExchangeRateSnapshot, Network, UniswapPool } from '../../generated/schema' import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { ASSETS_USD_PRICE_FEED, @@ -18,7 +18,7 @@ import { isGnosisNetwork } from './network' const latestAnswerSelector = '0x50d25bcd' -export function updateExchangeRates(network: Network): void { +export function updateExchangeRates(network: Network, timestamp: BigInt): void { if (NETWORK == 'chiado' || NETWORK == 'holesky') { return } @@ -130,4 +130,18 @@ export function updateExchangeRates(network: Network): void { network.daiUsdRate = daiUsdRate network.usdcUsdRate = usdcUsdRate network.save() + + const exchangeRateSnapshot = new ExchangeRateSnapshot(timestamp.toString()) + exchangeRateSnapshot.timestamp = timestamp.toI64() + exchangeRateSnapshot.assetsUsdRate = assetsUsdRate + exchangeRateSnapshot.swiseUsdRate = swiseUsdRate + exchangeRateSnapshot.daiUsdRate = daiUsdRate + exchangeRateSnapshot.usdcUsdRate = usdcUsdRate + exchangeRateSnapshot.usdToEurRate = usdToEurRate + exchangeRateSnapshot.usdToGbpRate = usdToGbpRate + exchangeRateSnapshot.usdToCnyRate = usdToCnyRate + exchangeRateSnapshot.usdToJpyRate = usdToJpyRate + exchangeRateSnapshot.usdToKrwRate = usdToKrwRate + exchangeRateSnapshot.usdToAudRate = usdToAudRate + exchangeRateSnapshot.save() } diff --git a/src/entities/vault.ts b/src/entities/vault.ts index d75d9d2..de4e12a 100644 --- a/src/entities/vault.ts +++ b/src/entities/vault.ts @@ -386,14 +386,10 @@ export function updateVaultMaxBoostApy( : osTokenConfig.leverageMaxMintLtvPercent const aaveLeverageLtv = aave.leverageMaxBorrowLtvPercent if (vaultLeverageLtv.isZero() || aaveLeverageLtv.isZero()) { - log.warning('[Vault] updateVaultMaxBoostApy ltvPercent is zero vault={} vaultLeverageLtv={} aaveLeverageLtv={}', [ - vault.id, - vaultLeverageLtv.toString(), - aaveLeverageLtv.toString(), - ]) vault.allocatorMaxBoostApy = BigDecimal.zero() vault.osTokenHolderMaxBoostApy = BigDecimal.zero() vault.save() + return } // calculate vault staking rate and the rate paid for minting osToken diff --git a/src/mappings/exchangeRates.ts b/src/mappings/exchangeRates.ts index e2f0042..328a9b9 100644 --- a/src/mappings/exchangeRates.ts +++ b/src/mappings/exchangeRates.ts @@ -4,7 +4,7 @@ import { updateExchangeRates } from '../entities/exchangeRates' export function handleExchangeRates(block: ethereum.Block): void { const network = loadNetwork()! - updateExchangeRates(network) + updateExchangeRates(network, block.timestamp) log.info( '[ExchangeRates] assetsUsdRate={} usdToEurRate={} usdToGbpRate={} usdToCnyRate={} usdToJpyRate={} usdToKrwRate={} usdToAudRate={} daiUsdRate={} usdcUsdRate={} swiseUsdRate={} timestamp={}', diff --git a/src/schema.graphql b/src/schema.graphql index c7cc773..c700976 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -883,6 +883,42 @@ type VaultStats @aggregation(intervals: ["day"], source: "VaultSnapshot") { apy: BigDecimal! @aggregate(fn: "last", arg: "apy") } +""" +The snapshot of the exchange rates +""" +type ExchangeRateSnapshot @entity(timeseries: true) { + id: Int8! + timestamp: Timestamp! + assetsUsdRate: BigDecimal! + swiseUsdRate: BigDecimal! + daiUsdRate: BigDecimal! + usdcUsdRate: BigDecimal! + usdToEurRate: BigDecimal! + usdToGbpRate: BigDecimal! + usdToCnyRate: BigDecimal! + usdToJpyRate: BigDecimal! + usdToKrwRate: BigDecimal! + usdToAudRate: BigDecimal! +} + +""" +The aggregation of the ExchangeRate snapshots +""" +type ExchangeRateStats @aggregation(intervals: ["day"], source: "ExchangeRateSnapshot") { + id: Int8! + timestamp: Timestamp! + assetsUsdRate: BigDecimal! @aggregate(fn: "last", arg: "assetsUsdRate") + swiseUsdRate: BigDecimal! @aggregate(fn: "last", arg: "swiseUsdRate") + daiUsdRate: BigDecimal! @aggregate(fn: "last", arg: "daiUsdRate") + usdcUsdRate: BigDecimal! @aggregate(fn: "last", arg: "usdcUsdRate") + usdToEurRate: BigDecimal! @aggregate(fn: "last", arg: "usdToEurRate") + usdToGbpRate: BigDecimal! @aggregate(fn: "last", arg: "usdToGbpRate") + usdToCnyRate: BigDecimal! @aggregate(fn: "last", arg: "usdToCnyRate") + usdToJpyRate: BigDecimal! @aggregate(fn: "last", arg: "usdToJpyRate") + usdToKrwRate: BigDecimal! @aggregate(fn: "last", arg: "usdToKrwRate") + usdToAudRate: BigDecimal! @aggregate(fn: "last", arg: "usdToAudRate") +} + "Uniswap V3 pool data" type UniswapPool @entity { "The address of the pool" From 1a47e26f6673f5d0dca37e6b73dfb1298560c878 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Sun, 5 Jan 2025 20:14:04 +0200 Subject: [PATCH 10/17] Fix strategy registry event --- src/mappings/strategiesRegistry.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mappings/strategiesRegistry.ts b/src/mappings/strategiesRegistry.ts index c6123bb..00082e4 100644 --- a/src/mappings/strategiesRegistry.ts +++ b/src/mappings/strategiesRegistry.ts @@ -9,10 +9,14 @@ export function handleStrategyConfigUpdated(event: StrategyConfigUpdated): void const configName = event.params.configName const value = event.params.value - if (configName == 'leverageMaxBorrowLtvPercent') { + if (configName == 'maxBorrowLtvPercent') { const aave = loadAave()! aave.leverageMaxBorrowLtvPercent = ethereum.decode('uint256', value)!.toBigInt() aave.save() + log.info('[StrategiesRegistry] StrategyConfigUpdated configName={} leverageMaxBorrowLtvPercent={}', [ + configName, + aave.leverageMaxBorrowLtvPercent.toString(), + ]) } else if (configName == 'maxVaultLtvPercent') { const leverageMaxMinLtvPercent = ethereum.decode('uint256', value)!.toBigInt() let osTokenConfig = loadOsTokenConfig('2')! @@ -29,7 +33,11 @@ export function handleStrategyConfigUpdated(event: StrategyConfigUpdated): void osTokenConfig.save() } } + log.info('[StrategiesRegistry] StrategyConfigUpdated configName={} maxVaultLtvPercent={}', [ + configName, + leverageMaxMinLtvPercent.toString(), + ]) + } else { + log.info('[StrategiesRegistry] StrategyConfigUpdated configName={}', [configName]) } - - log.info('[StrategiesRegistry] StrategyConfigUpdated configName={}', [configName]) } From f7958290245a85af9a8c571fb2573da693a1daea Mon Sep 17 00:00:00 2001 From: Evgeny Gusarov Date: Fri, 3 Jan 2025 17:03:08 +0300 Subject: [PATCH 11/17] Move latest rates from Network to ExchangeRate --- src/entities/exchangeRates.ts | 52 +++++++++++++++++------ src/entities/merkleDistributor.ts | 25 +++++++----- src/entities/network.ts | 12 +----- src/mappings/exchangeRates.ts | 27 ++++++------ src/mappings/merkleDistributor.ts | 8 ++-- src/mappings/periodicTasks.ts | 6 ++- src/schema.graphql | 68 +++++++++++++++++-------------- src/subgraph.template.yaml | 4 +- 8 files changed, 117 insertions(+), 85 deletions(-) diff --git a/src/entities/exchangeRates.ts b/src/entities/exchangeRates.ts index b2246f7..5c92437 100644 --- a/src/entities/exchangeRates.ts +++ b/src/entities/exchangeRates.ts @@ -1,4 +1,4 @@ -import { ExchangeRateSnapshot, Network, UniswapPool } from '../../generated/schema' +import { ExchangeRateSnapshot, UniswapPool, ExchangeRate } from '../../generated/schema' import { Address, BigDecimal, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' import { ASSETS_USD_PRICE_FEED, @@ -17,8 +17,9 @@ import { chunkedMulticall } from '../helpers/utils' import { isGnosisNetwork } from './network' const latestAnswerSelector = '0x50d25bcd' +const exchangeRateId = '0' -export function updateExchangeRates(network: Network, timestamp: BigInt): void { +export function updateExchangeRates(exchangeRate: ExchangeRate, timestamp: BigInt): void { if (NETWORK == 'chiado' || NETWORK == 'holesky') { return } @@ -119,17 +120,17 @@ export function updateExchangeRates(network: Network, timestamp: BigInt): void { const usdToKrwRate = krwToUsdRate.gt(zero) ? one.div(krwToUsdRate) : zero const usdToAudRate = audToUsdRate.gt(zero) ? one.div(audToUsdRate) : zero - network.assetsUsdRate = assetsUsdRate - network.swiseUsdRate = swiseUsdRate - network.usdToEurRate = usdToEurRate - network.usdToGbpRate = usdToGbpRate - network.usdToCnyRate = usdToCnyRate - network.usdToJpyRate = usdToJpyRate - network.usdToKrwRate = usdToKrwRate - network.usdToAudRate = usdToAudRate - network.daiUsdRate = daiUsdRate - network.usdcUsdRate = usdcUsdRate - network.save() + exchangeRate.assetsUsdRate = assetsUsdRate + exchangeRate.swiseUsdRate = swiseUsdRate + exchangeRate.usdToEurRate = usdToEurRate + exchangeRate.usdToGbpRate = usdToGbpRate + exchangeRate.usdToCnyRate = usdToCnyRate + exchangeRate.usdToJpyRate = usdToJpyRate + exchangeRate.usdToKrwRate = usdToKrwRate + exchangeRate.usdToAudRate = usdToAudRate + exchangeRate.daiUsdRate = daiUsdRate + exchangeRate.usdcUsdRate = usdcUsdRate + exchangeRate.save() const exchangeRateSnapshot = new ExchangeRateSnapshot(timestamp.toString()) exchangeRateSnapshot.timestamp = timestamp.toI64() @@ -145,3 +146,28 @@ export function updateExchangeRates(network: Network, timestamp: BigInt): void { exchangeRateSnapshot.usdToAudRate = usdToAudRate exchangeRateSnapshot.save() } + +export function createOrLoadExchangeRate(): ExchangeRate { + let exchangeRate = loadExchangeRate() + + if (exchangeRate === null) { + exchangeRate = new ExchangeRate(exchangeRateId) + exchangeRate.assetsUsdRate = BigDecimal.zero() + exchangeRate.swiseUsdRate = BigDecimal.zero() + exchangeRate.daiUsdRate = BigDecimal.zero() + exchangeRate.usdcUsdRate = BigDecimal.zero() + exchangeRate.usdToEurRate = BigDecimal.zero() + exchangeRate.usdToGbpRate = BigDecimal.zero() + exchangeRate.usdToCnyRate = BigDecimal.zero() + exchangeRate.usdToJpyRate = BigDecimal.zero() + exchangeRate.usdToKrwRate = BigDecimal.zero() + exchangeRate.usdToAudRate = BigDecimal.zero() + exchangeRate.save() + } + + return exchangeRate +} + +export function loadExchangeRate(): ExchangeRate | null { + return ExchangeRate.load(exchangeRateId) +} diff --git a/src/entities/merkleDistributor.ts b/src/entities/merkleDistributor.ts index c7fa0a7..3f3a25e 100644 --- a/src/entities/merkleDistributor.ts +++ b/src/entities/merkleDistributor.ts @@ -3,6 +3,7 @@ import { Distributor, DistributorClaim, DistributorReward, + ExchangeRate, LeverageStrategyPosition, Network, OsToken, @@ -172,6 +173,7 @@ export function convertStringToDistributionType(distTypeString: string): Distrib export function updateDistributions( network: Network, + exchangeRate: ExchangeRate, osToken: OsToken, distributor: Distributor, currentTimestamp: BigInt, @@ -216,7 +218,7 @@ export function updateDistributions( // dist data is the pool address const uniPool = loadUniswapPool(Address.fromBytes(dist.data))! principalAssets = distributeToSwiseAssetUniPoolUsers( - network, + exchangeRate, uniPool, Address.fromBytes(dist.token), distributedAmount, @@ -226,7 +228,7 @@ export function updateDistributions( // dist data is the pool address const uniPool = loadUniswapPool(Address.fromBytes(dist.data))! principalAssets = distributeToOsTokenUsdcUniPoolUsers( - network, + exchangeRate, osToken, uniPool, Address.fromBytes(dist.token), @@ -241,8 +243,11 @@ export function updateDistributions( let distributedAssets: BigInt = BigInt.zero() if (dist.token.equals(OS_TOKEN)) { distributedAssets = convertOsTokenSharesToAssets(osToken, distributedAmount) - } else if (dist.token.equals(SWISE_TOKEN) && network.assetsUsdRate.gt(BigDecimal.zero())) { - distributedAssets = distributedAmount.toBigDecimal().times(network.swiseUsdRate).div(network.assetsUsdRate).digits + } else if (dist.token.equals(SWISE_TOKEN) && exchangeRate.assetsUsdRate.gt(BigDecimal.zero())) { + distributedAssets = distributedAmount + .toBigDecimal() + .times(exchangeRate.swiseUsdRate) + .div(exchangeRate.assetsUsdRate).digits } else { log.error('[MerkleDistributor] Unknown token={} price to update APY', [dist.token.toHex()]) } @@ -294,15 +299,15 @@ export function updatePeriodicDistributionApy( } export function distributeToSwiseAssetUniPoolUsers( - network: Network, + exchangeRate: ExchangeRate, pool: UniswapPool, token: Address, totalReward: BigInt, ): BigInt { const swiseToken = SWISE_TOKEN const assetToken = Address.fromString(ASSET_TOKEN) - const swiseUsdRate = network.swiseUsdRate - const assetsUsdRate = network.assetsUsdRate + const swiseUsdRate = exchangeRate.swiseUsdRate + const assetsUsdRate = exchangeRate.assetsUsdRate if ( (pool.token0.notEqual(swiseToken) && pool.token1.notEqual(assetToken)) || (pool.token0.notEqual(assetToken) && pool.token1.notEqual(swiseToken)) @@ -352,7 +357,7 @@ export function distributeToSwiseAssetUniPoolUsers( } export function distributeToOsTokenUsdcUniPoolUsers( - network: Network, + exchangeRate: ExchangeRate, osToken: OsToken, pool: UniswapPool, token: Address, @@ -365,8 +370,8 @@ export function distributeToOsTokenUsdcUniPoolUsers( ) { assert(false, "Pool doesn't contain USDC and OsToken tokens") } - const assetsUsdRate = network.assetsUsdRate - const usdcUsdRate = network.usdcUsdRate + const assetsUsdRate = exchangeRate.assetsUsdRate + const usdcUsdRate = exchangeRate.usdcUsdRate if (assetsUsdRate.equals(BigDecimal.zero()) || usdcUsdRate.equals(BigDecimal.zero())) { assert(false, 'Missing USD rates for OsToken or USDC token') } diff --git a/src/entities/network.ts b/src/entities/network.ts index 55da025..9c20df9 100644 --- a/src/entities/network.ts +++ b/src/entities/network.ts @@ -1,4 +1,4 @@ -import { Address, BigDecimal, BigInt, Bytes, store } from '@graphprotocol/graph-ts' +import { Address, BigInt, Bytes, store } from '@graphprotocol/graph-ts' import { Network, RewardSplitter, User, Vault } from '../../generated/schema' import { NETWORK, V2_REWARD_TOKEN, V2_STAKED_TOKEN } from '../helpers/constants' @@ -21,16 +21,6 @@ export function createOrLoadNetwork(): Network { network.osTokenVaultIds = [] network.oraclesConfigIpfsHash = '' network.snapshotsCount = BigInt.zero() - network.assetsUsdRate = BigDecimal.zero() - network.swiseUsdRate = BigDecimal.zero() - network.daiUsdRate = BigDecimal.zero() - network.usdcUsdRate = BigDecimal.zero() - network.usdToEurRate = BigDecimal.zero() - network.usdToGbpRate = BigDecimal.zero() - network.usdToCnyRate = BigDecimal.zero() - network.usdToJpyRate = BigDecimal.zero() - network.usdToKrwRate = BigDecimal.zero() - network.usdToAudRate = BigDecimal.zero() network.save() } diff --git a/src/mappings/exchangeRates.ts b/src/mappings/exchangeRates.ts index 328a9b9..19c2b4e 100644 --- a/src/mappings/exchangeRates.ts +++ b/src/mappings/exchangeRates.ts @@ -1,24 +1,23 @@ import { ethereum, log } from '@graphprotocol/graph-ts' -import { loadNetwork } from '../entities/network' -import { updateExchangeRates } from '../entities/exchangeRates' +import { createOrLoadExchangeRate, updateExchangeRates } from '../entities/exchangeRates' export function handleExchangeRates(block: ethereum.Block): void { - const network = loadNetwork()! - updateExchangeRates(network, block.timestamp) + const exchangeRate = createOrLoadExchangeRate() + updateExchangeRates(exchangeRate, block.timestamp) log.info( '[ExchangeRates] assetsUsdRate={} usdToEurRate={} usdToGbpRate={} usdToCnyRate={} usdToJpyRate={} usdToKrwRate={} usdToAudRate={} daiUsdRate={} usdcUsdRate={} swiseUsdRate={} timestamp={}', [ - network.assetsUsdRate.toString(), - network.usdToEurRate.toString(), - network.usdToGbpRate.toString(), - network.usdToCnyRate.toString(), - network.usdToJpyRate.toString(), - network.usdToKrwRate.toString(), - network.usdToAudRate.toString(), - network.daiUsdRate.toString(), - network.usdcUsdRate.toString(), - network.swiseUsdRate.toString(), + exchangeRate.assetsUsdRate.toString(), + exchangeRate.usdToEurRate.toString(), + exchangeRate.usdToGbpRate.toString(), + exchangeRate.usdToCnyRate.toString(), + exchangeRate.usdToJpyRate.toString(), + exchangeRate.usdToKrwRate.toString(), + exchangeRate.usdToAudRate.toString(), + exchangeRate.daiUsdRate.toString(), + exchangeRate.usdcUsdRate.toString(), + exchangeRate.swiseUsdRate.toString(), block.timestamp.toString(), ], ) diff --git a/src/mappings/merkleDistributor.ts b/src/mappings/merkleDistributor.ts index 1d7cc3b..8a334c8 100644 --- a/src/mappings/merkleDistributor.ts +++ b/src/mappings/merkleDistributor.ts @@ -17,14 +17,14 @@ import { } from '../entities/merkleDistributor' import { createTransaction } from '../entities/transaction' import { OS_TOKEN } from '../helpers/constants' -import { loadNetwork } from '../entities/network' +import { loadExchangeRate } from '../entities/exchangeRates' const secondsInYear = '31536000' export function handlePeriodicDistributionAdded(event: PeriodicDistributionAdded): void { const token = event.params.token const extraData = event.params.extraData - const network = loadNetwork()! + const exchangeRate = loadExchangeRate()! const distType = getDistributionType(extraData) if (distType == DistributionType.UNKNOWN) { @@ -35,13 +35,13 @@ export function handlePeriodicDistributionAdded(event: PeriodicDistributionAdded return } else if ( distType == DistributionType.SWISE_ASSET_UNI_POOL && - (network.assetsUsdRate.equals(BigDecimal.zero()) || network.swiseUsdRate.equals(BigDecimal.zero())) + (exchangeRate.assetsUsdRate.equals(BigDecimal.zero()) || exchangeRate.swiseUsdRate.equals(BigDecimal.zero())) ) { log.error('[MerkleDistributor] Swise asset Uni pool distribution assetsUsdRate or swiseUsdRate is zero', []) return } else if ( distType == DistributionType.OS_TOKEN_USDC_UNI_POOL && - (network.assetsUsdRate.equals(BigDecimal.zero()) || network.usdcUsdRate.equals(BigDecimal.zero())) + (exchangeRate.assetsUsdRate.equals(BigDecimal.zero()) || exchangeRate.usdcUsdRate.equals(BigDecimal.zero())) ) { log.error('[MerkleDistributor] OsToken USDC Uni pool distribution assetsUsdRate or usdcUsdRate is zero', []) return diff --git a/src/mappings/periodicTasks.ts b/src/mappings/periodicTasks.ts index 17d88d3..b43fdeb 100644 --- a/src/mappings/periodicTasks.ts +++ b/src/mappings/periodicTasks.ts @@ -11,13 +11,15 @@ import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { loadOsTokenConfig } from '../entities/osTokenConfig' import { loadAave, updateAaveApys, updateAavePositions } from '../entities/aave' import { loadDistributor, updateDistributions } from '../entities/merkleDistributor' +import { loadExchangeRate } from '../entities/exchangeRates' export function handlePeriodicTasks(block: ethereum.Block): void { const timestamp = block.timestamp const blockNumber = block.number const network = loadNetwork() + const exchangeRate = loadExchangeRate() const aave = loadAave() - if (!network || !aave) { + if (!network || !exchangeRate || !aave) { return } @@ -41,7 +43,7 @@ export function handlePeriodicTasks(block: ethereum.Block): void { // update distributions // NB! if blocksInHour config is updated, the average apy calculation must be updated const distributor = loadDistributor()! - updateDistributions(network, osToken, distributor, timestamp) + updateDistributions(network, exchangeRate, osToken, distributor, timestamp) const vaultIds = network.vaultIds let vaultAddress: Address diff --git a/src/schema.graphql b/src/schema.graphql index c700976..177daff 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -447,36 +447,6 @@ type Network @entity { "Total number of vaults" vaultsCount: Int! - "The USD rate of the assets (e.g. ETH, GNO)" - assetsUsdRate: BigDecimal! - - "The USD rate of the SWISE" - swiseUsdRate: BigDecimal! - - "The USD rate of DAI" - daiUsdRate: BigDecimal! - - "The USD rate of USDC" - usdcUsdRate: BigDecimal! - - "The USD to EUR rate" - usdToEurRate: BigDecimal! - - "The USD to GBP rate" - usdToGbpRate: BigDecimal! - - "The USD to CNY rate" - usdToCnyRate: BigDecimal! - - "The USD to JPY rate" - usdToJpyRate: BigDecimal! - - "The USD to KRW rate" - usdToKrwRate: BigDecimal! - - "The USD to AUD rate" - usdToAudRate: BigDecimal! - "The non repeated addresses of all the vaults" vaultIds: [String!]! @@ -883,6 +853,44 @@ type VaultStats @aggregation(intervals: ["day"], source: "VaultSnapshot") { apy: BigDecimal! @aggregate(fn: "last", arg: "apy") } +""" +The latest exchange rates +""" +type ExchangeRate @entity { + "Always 0" + id: ID! + + "The USD rate of the assets (e.g. ETH, GNO)" + assetsUsdRate: BigDecimal! + + "The USD rate of the SWISE" + swiseUsdRate: BigDecimal! + + "The USD rate of DAI" + daiUsdRate: BigDecimal! + + "The USD rate of USDC" + usdcUsdRate: BigDecimal! + + "The USD to EUR rate" + usdToEurRate: BigDecimal! + + "The USD to GBP rate" + usdToGbpRate: BigDecimal! + + "The USD to CNY rate" + usdToCnyRate: BigDecimal! + + "The USD to JPY rate" + usdToJpyRate: BigDecimal! + + "The USD to KRW rate" + usdToKrwRate: BigDecimal! + + "The USD to AUD rate" + usdToAudRate: BigDecimal! +} + """ The snapshot of the exchange rates """ diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 7643a06..4b017b3 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -74,7 +74,9 @@ dataSources: language: wasm/assemblyscript file: ./mappings/exchangeRates.ts entities: - - Network + - ExchangeRate + - ExchangeRateSnapshot + - ExchangeRateStats abis: - name: PriceFeed file: ./abis/PriceFeed.json From f529b376706d899f99e6b5800a3175e0bb677b06 Mon Sep 17 00:00:00 2001 From: Evgeny Gusarov Date: Fri, 3 Jan 2025 17:03:23 +0300 Subject: [PATCH 12/17] Del .eslintignore --- .eslintignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 08ae113..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -generated/ -node_modules/ From 67874591f71306af1430cca51d8630e31015b63b Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Mon, 6 Jan 2025 12:36:15 +0200 Subject: [PATCH 13/17] Check for null distributor --- src/mappings/keeper.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mappings/keeper.ts b/src/mappings/keeper.ts index ad444fc..fbfcb5f 100644 --- a/src/mappings/keeper.ts +++ b/src/mappings/keeper.ts @@ -403,7 +403,10 @@ export function handleDailySnapshots(block: ethereum.Block): void { osToken.save() let osTokenHolder: OsTokenHolder - const distributor = loadDistributor()! + const distributor = loadDistributor() + if (!distributor) { + return + } const osTokenHolders: Array = osToken.holders.load() for (let i = 0; i < osTokenHolders.length; i++) { osTokenHolder = osTokenHolders[i] From 02edd660a2a0ccafe99728ab62c18af40f04dd59 Mon Sep 17 00:00:00 2001 From: Evgeny Gusarov Date: Mon, 6 Jan 2025 13:40:08 +0300 Subject: [PATCH 14/17] Add ExchangeRate to PeriodicTasks entities --- src/subgraph.template.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 4b017b3..8ec124d 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -22,6 +22,7 @@ dataSources: file: ./mappings/periodicTasks.ts entities: - Network + - ExchangeRate - Aave - AavePosition - OsToken From 30087ff2a41506fb5ac9e1a91d7cd4cfcef3ed47 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Mon, 6 Jan 2025 17:34:23 +0200 Subject: [PATCH 15/17] Merge daily snapshots with periodic tasks, remove args filter for uniswap --- src/mappings/keeper.ts | 75 +++-------------------------------- src/mappings/periodicTasks.ts | 72 ++++++++++++++++++++++++++++++--- src/subgraph.template.yaml | 4 -- 3 files changed, 71 insertions(+), 80 deletions(-) diff --git a/src/mappings/keeper.ts b/src/mappings/keeper.ts index fbfcb5f..2920894 100644 --- a/src/mappings/keeper.ts +++ b/src/mappings/keeper.ts @@ -1,14 +1,4 @@ -import { - Address, - BigInt, - Bytes, - DataSourceContext, - ethereum, - ipfs, - json, - JSONValue, - log, -} from '@graphprotocol/graph-ts' +import { Address, BigInt, Bytes, DataSourceContext, ipfs, json, JSONValue, log } from '@graphprotocol/graph-ts' import { BLOCKLIST_ERC20_VAULT_FACTORY_V2, BLOCKLIST_ERC20_VAULT_FACTORY_V3, @@ -36,8 +26,8 @@ import { RewardSplitterFactory as RewardSplitterFactoryTemplate, VaultFactory as VaultFactoryTemplate, } from '../../generated/templates' -import { Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' -import { createOrLoadOsToken, loadOsToken, snapshotOsToken, updateOsTokenApy } from '../entities/osToken' +import { Allocator, OsTokenHolder } from '../../generated/schema' +import { createOrLoadOsToken, loadOsToken, updateOsTokenApy } from '../entities/osToken' import { createOrLoadAllocator, getAllocatorApy, snapshotAllocator, updateAllocatorAssets } from '../entities/allocator' import { createOrLoadNetwork, increaseUserVaultsCount, loadNetwork } from '../entities/network' import { @@ -53,15 +43,14 @@ import { updateExitRequests } from '../entities/exitRequest' import { updateRewardSplitters } from '../entities/rewardSplitter' import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' -import { loadVault, snapshotVault, updateVaultMaxBoostApy, updateVaults } from '../entities/vault' -import { getOsTokenHolderApy, snapshotOsTokenHolder } from '../entities/osTokenHolder' +import { loadVault, updateVaultMaxBoostApy, updateVaults } from '../entities/vault' +import { getOsTokenHolderApy } from '../entities/osTokenHolder' import { createOrLoadAave, loadAave } from '../entities/aave' import { createOrLoadDistributor, loadDistributor } from '../entities/merkleDistributor' const IS_PRIVATE_KEY = 'isPrivate' const IS_ERC20_KEY = 'isErc20' const IS_BLOCKLIST_KEY = 'isBlocklist' -const secondsInDay = 86400 export function handleOwnershipTransferred(event: OwnershipTransferred): void { createOrLoadV2Pool() @@ -379,57 +368,3 @@ export function handleConfigUpdated(event: ConfigUpdated): void { network.save() log.info('[Keeper] ConfigUpdated configIpfsHash={}', [configIpfsHash]) } - -export function handleDailySnapshots(block: ethereum.Block): void { - const network = loadNetwork() - if (!network) { - return - } - - const timestamp = block.timestamp - const newSnapshotsCount = timestamp.plus(BigInt.fromI32(30)).div(BigInt.fromI32(secondsInDay)) - if (newSnapshotsCount.le(network.snapshotsCount)) { - return - } - network.snapshotsCount = newSnapshotsCount - network.save() - - const osToken = loadOsToken() - if (!osToken) { - return - } - snapshotOsToken(osToken, osToken._periodEarnedAssets, timestamp) - osToken._periodEarnedAssets = BigInt.zero() - osToken.save() - - let osTokenHolder: OsTokenHolder - const distributor = loadDistributor() - if (!distributor) { - return - } - const osTokenHolders: Array = osToken.holders.load() - for (let i = 0; i < osTokenHolders.length; i++) { - osTokenHolder = osTokenHolders[i] - snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, osTokenHolder._periodEarnedAssets, timestamp) - osTokenHolder._periodEarnedAssets = BigInt.zero() - osTokenHolder.save() - } - - let vault: Vault - let osTokenConfig: OsTokenConfig - const vaultIds = network.vaultIds - for (let i = 0; i < vaultIds.length; i++) { - vault = loadVault(Address.fromString(vaultIds[i]))! - osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! - snapshotVault(vault, BigInt.zero(), timestamp) - - const allocators: Array = vault.allocators.load() - for (let j = 0; j < allocators.length; j++) { - const allocator = allocators[j] - snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, allocator._periodEarnedAssets, timestamp) - allocator._periodEarnedAssets = BigInt.zero() - allocator.save() - } - } - log.info('[DailySnapshots] block={} timestamp={}', [block.number.toString(), timestamp.toString()]) -} diff --git a/src/mappings/periodicTasks.ts b/src/mappings/periodicTasks.ts index 17d88d3..59f407b 100644 --- a/src/mappings/periodicTasks.ts +++ b/src/mappings/periodicTasks.ts @@ -1,17 +1,25 @@ -import { Address, ethereum, log } from '@graphprotocol/graph-ts' -import { loadVault, updateVaultMaxBoostApy } from '../entities/vault' -import { loadOsToken, updateOsTokenTotalAssets } from '../entities/osToken' +import { Address, BigInt, ethereum, log } from '@graphprotocol/graph-ts' +import { loadVault, snapshotVault, updateVaultMaxBoostApy } from '../entities/vault' +import { loadOsToken, snapshotOsToken, updateOsTokenTotalAssets } from '../entities/osToken' import { loadNetwork } from '../entities/network' -import { Allocator, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' -import { getAllocatorApy, getAllocatorsMintedShares, updateAllocatorMintedOsTokenShares } from '../entities/allocator' +import { Allocator, Network, OsTokenConfig, OsTokenHolder, Vault } from '../../generated/schema' +import { + getAllocatorApy, + getAllocatorsMintedShares, + snapshotAllocator, + updateAllocatorMintedOsTokenShares, +} from '../entities/allocator' import { updateExitRequests } from '../entities/exitRequest' -import { getOsTokenHolderApy, updateOsTokenHolderAssets } from '../entities/osTokenHolder' +import { getOsTokenHolderApy, snapshotOsTokenHolder, updateOsTokenHolderAssets } from '../entities/osTokenHolder' import { updateOsTokenExitRequests } from '../entities/osTokenVaultEscrow' import { updateLeverageStrategyPositions } from '../entities/leverageStrategy' import { loadOsTokenConfig } from '../entities/osTokenConfig' import { loadAave, updateAaveApys, updateAavePositions } from '../entities/aave' import { loadDistributor, updateDistributions } from '../entities/merkleDistributor' +const secondsInHour = 3600 +const secondsInDay = 86400 + export function handlePeriodicTasks(block: ethereum.Block): void { const timestamp = block.timestamp const blockNumber = block.number @@ -87,5 +95,57 @@ export function handlePeriodicTasks(block: ethereum.Block): void { osTokenHolder.apy = getOsTokenHolderApy(network, osToken, distributor, osTokenHolder, false) osTokenHolder.save() } + + // Update snapshots + _updateSnapshots(network, timestamp) + log.info('[PeriodicTasks] block={} timestamp={}', [blockNumber.toString(), timestamp.toString()]) } + +function _updateSnapshots(network: Network, timestamp: BigInt): void { + const newSnapshotsCount = timestamp.plus(BigInt.fromI32(secondsInHour)).div(BigInt.fromI32(secondsInDay)) + if (newSnapshotsCount.le(network.snapshotsCount)) { + return + } + network.snapshotsCount = newSnapshotsCount + network.save() + + const osToken = loadOsToken() + if (!osToken) { + return + } + snapshotOsToken(osToken, osToken._periodEarnedAssets, timestamp) + osToken._periodEarnedAssets = BigInt.zero() + osToken.save() + + let osTokenHolder: OsTokenHolder + const distributor = loadDistributor() + if (!distributor) { + return + } + const osTokenHolders: Array = osToken.holders.load() + for (let i = 0; i < osTokenHolders.length; i++) { + osTokenHolder = osTokenHolders[i] + snapshotOsTokenHolder(network, osToken, distributor, osTokenHolder, osTokenHolder._periodEarnedAssets, timestamp) + osTokenHolder._periodEarnedAssets = BigInt.zero() + osTokenHolder.save() + } + + let vault: Vault + let osTokenConfig: OsTokenConfig + const vaultIds = network.vaultIds + for (let i = 0; i < vaultIds.length; i++) { + vault = loadVault(Address.fromString(vaultIds[i]))! + osTokenConfig = loadOsTokenConfig(vault.osTokenConfig)! + snapshotVault(vault, BigInt.zero(), timestamp) + + const allocators: Array = vault.allocators.load() + for (let j = 0; j < allocators.length; j++) { + const allocator = allocators[j] + snapshotAllocator(osToken, osTokenConfig, vault, distributor, allocator, allocator._periodEarnedAssets, timestamp) + allocator._periodEarnedAssets = BigInt.zero() + allocator.save() + } + } + log.info('[PeriodicTasks] snapshots updated timestamp={}', [timestamp.toString()]) +} diff --git a/src/subgraph.template.yaml b/src/subgraph.template.yaml index 7643a06..3082476 100644 --- a/src/subgraph.template.yaml +++ b/src/subgraph.template.yaml @@ -190,8 +190,6 @@ dataSources: handler: handleValidatorsApproval - event: ConfigUpdated(string) handler: handleConfigUpdated - blockHandlers: - - handler: handleDailySnapshots - kind: ethereum/contract name: OsTokenVaultController network: {{ network }} @@ -507,8 +505,6 @@ dataSources: eventHandlers: - event: PoolCreated(indexed address,indexed address,indexed uint24,int24,address) handler: handlePoolCreated - topic1: ['{{ osToken.address }}', '{{ swiseToken.address }}'] - topic2: ['{{ osToken.address }}', '{{ swiseToken.address }}'] - kind: ethereum/contract name: UniswapPositionManager network: {{ network }} From 9a756d324b6382dcf0cbde2abb2656bb202c4e2e Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Mon, 6 Jan 2025 18:02:25 +0200 Subject: [PATCH 16/17] Fix start blocks --- src/config/chiado.json | 2 +- src/config/gnosis.json | 2 +- src/config/mainnet.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/chiado.json b/src/config/chiado.json index 86fbc9f..729410d 100644 --- a/src/config/chiado.json +++ b/src/config/chiado.json @@ -109,6 +109,6 @@ }, "strategiesRegistry": { "address": "0x4abB9BBb82922A6893A5d6890cd2eE94610BEc48", - "startBlock": "36651385" + "startBlock": "12438574" } } diff --git a/src/config/gnosis.json b/src/config/gnosis.json index b737ad3..c74dfe3 100644 --- a/src/config/gnosis.json +++ b/src/config/gnosis.json @@ -109,6 +109,6 @@ }, "strategiesRegistry": { "address": "0x4abB9BBb82922A6893A5d6890cd2eE94610BEc48", - "startBlock": "12438574" + "startBlock": "36651385" } } diff --git a/src/config/mainnet.json b/src/config/mainnet.json index 60f69b5..4212bf1 100644 --- a/src/config/mainnet.json +++ b/src/config/mainnet.json @@ -81,7 +81,7 @@ }, "osTokenVaultEscrow": { "address": "0x09e84205DF7c68907e619D07aFD90143c5763605", - "startBlock": "2592222" + "startBlock": "21034500" }, "merkleDistributor": { "address": "0xA593948a0bC611fC6945eA013806b0191aE79B47", From fc62b8ca12c756a7c61873ed76d0b1d8ac9f50db Mon Sep 17 00:00:00 2001 From: Evgeny Gusarov Date: Wed, 15 Jan 2025 17:01:33 +0300 Subject: [PATCH 17/17] Fix merge --- src/entities/allocator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/allocator.ts b/src/entities/allocator.ts index a40ceb3..38241a9 100644 --- a/src/entities/allocator.ts +++ b/src/entities/allocator.ts @@ -11,7 +11,7 @@ import { Vault, } from '../../generated/schema' import { WAD } from '../helpers/constants' -import { calculateApy, chunkedVaultMulticall, chunkedVaultMulticall, getAnnualReward } from '../helpers/utils' +import { calculateApy, chunkedVaultMulticall, getAnnualReward } from '../helpers/utils' import { convertOsTokenSharesToAssets, getOsTokenApy } from './osToken' import { convertSharesToAssets, getVaultApy, getVaultOsTokenMintApy, loadVault } from './vault' import { loadOsTokenConfig } from './osTokenConfig'