diff --git a/package.json b/package.json index 0b2237b3e..0b98b1b1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.50.8", + "version": "2.50.9", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/src/abi/angle/stagToken.json b/src/abi/angle/stagToken.json new file mode 100644 index 000000000..1b9bf59ef --- /dev/null +++ b/src/abi/angle/stagToken.json @@ -0,0 +1,963 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidRate", + "type": "error" + }, + { + "inputs": [], + "name": "NotGovernor", + "type": "error" + }, + { + "inputs": [], + "name": "NotGovernorOrGuardian", + "type": "error" + }, + { + "inputs": [], + "name": "Paused", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "interest", + "type": "uint256" + } + ], + "name": "Accrued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newMaxRate", + "type": "uint256" + } + ], + "name": "MaxRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "RateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint128", + "name": "pauseStatus", + "type": "uint128" + } + ], + "name": "ToggledPause", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "asset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_totalAssets", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exp", + "type": "uint256" + } + ], + "name": "computeUpdatedAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "estimatedAPR", + "outputs": [ + { + "internalType": "uint256", + "name": "apr", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAccessControlManager", + "name": "_accessControlManager", + "type": "address" + }, + { + "internalType": "contract IERC20MetadataUpgradeable", + "name": "asset_", + "type": "address" + }, + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + }, + { + "internalType": "uint256", + "name": "divizer", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "isGovernor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "isGovernorOrGuardian", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastUpdate", + "outputs": [ + { + "internalType": "uint40", + "name": "", + "type": "uint40" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "maxDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "maxMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "maxWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "previewRedeem", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "name": "previewWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rate", + "outputs": [ + { + "internalType": "uint208", + "name": "", + "type": "uint208" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "redeem", + "outputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMaxRate", + "type": "uint256" + } + ], + "name": "setMaxRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint208", + "name": "newRate", + "type": "uint208" + } + ], + "name": "setRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "togglePause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "assets", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/config.ts b/src/config.ts index 1bce97695..8457ffe06 100644 --- a/src/config.ts +++ b/src/config.ts @@ -170,8 +170,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_56`]?.split(',') || [], adapterAddresses: { BscAdapter01: '0xA31d9C571DF00e0F428B0bD24c34D103E8112222', - BscAdapter02: '0x9A92D2649C38415860FA59ba8B9a9960cd2839Db', - BscBuyAdapter: '0xd32C191e0febaa6Cc93A29Cb676474c72486E00b', + BscAdapter02: '0x7A8f0436981B893349514655a00Ca974A6e9fd93', + BscBuyAdapter: '0x2389726B55948d8D8944b0145204761215AaEc71', }, rpcPollingMaxAllowedStateDelayInBlocks: 1, rpcPollingBlocksBackToTriggerUpdate: 1, @@ -199,8 +199,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_137`]?.split(',') || [], adapterAddresses: { PolygonAdapter01: '0xE44769f42E1e9592f86B82f206407a8f7C84b4ed', - PolygonAdapter02: '0x84bEF12C9931cE12662cc9F2366b6a5029E4BD29', - PolygonBuyAdapter: '0xBAEeb4540f59d30E567a5B563CC0c4587eDd9366', + PolygonAdapter02: '0xE7d4CC1589311BD7Bb58739269748a20DAAD755D', + PolygonBuyAdapter: '0x8643Aa3E63dF7742223A713C7525677df336183f', }, uniswapV2ExchangeRouterAddress: '0xf3938337F7294fEf84e9B2c6D548A93F956Cc281', @@ -323,7 +323,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { process.env[`HASHFLOW_DISABLED_MMS_10`]?.split(',') || [], adapterAddresses: { OptimismAdapter01: '0x5dcf544b0c9689fa67dcb713fd2656d217e25a59', - OptimismBuyAdapter: '0xA10c9a84E72d9DfF424Fe2284B6460784bed407E', + OptimismAdapter02: '0x4483Ae378897eB9FbF7f15Df98Bf07233ffFEe8b', + OptimismBuyAdapter: '0xe9166234DFB6d3ec05C82404109C02Ca82b16c22', }, uniswapV2ExchangeRouterAddress: '0xB41dD984730dAf82f5C41489E21ac79D5e3B61bC', @@ -380,8 +381,8 @@ const baseConfigs: { [network: number]: BaseConfig } = { smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowDisabledMMs: [], adapterAddresses: { - BaseAdapter01: '0xf531fC01aFa26dAa71f581e1e18AA3B37Ee515A6', - BaseBuyAdapter: '0x2B99cefbfeE6f134D68aBB291386588ADe3808F8', + BaseAdapter01: '0xe53d24CD81cC81bbf271AD7B02D0d67f851D727c', + BaseBuyAdapter: '0xe07678E5Fd104cbabb239049148b8a4E9dA5d07E', }, uniswapV2ExchangeRouterAddress: '0x75d199EfB540e47D27D52c62Da3E7daC2B9e834F', diff --git a/src/dex/angle-staked-stable/angle-staked-stable-e2e.test.ts b/src/dex/angle-staked-stable/angle-staked-stable-e2e.test.ts new file mode 100644 index 000000000..173c5812d --- /dev/null +++ b/src/dex/angle-staked-stable/angle-staked-stable-e2e.test.ts @@ -0,0 +1,146 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + 1, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + 1, + ); + }); + }); + }); + }), + ); + }); +} + +describe('AngleStakedStable E2E', () => { + const networksEUR = [ + Network.MAINNET, + Network.ARBITRUM, + Network.POLYGON, + Network.OPTIMISM, + ]; + + const networksUSD = [ + Network.MAINNET, + Network.ARBITRUM, + Network.POLYGON, + Network.OPTIMISM, + Network.BASE, + Network.BSC, + ]; + + networksEUR.forEach(network => + describe(`${network} - EUR`, () => { + const dexKey = 'AngleStakedStableEUR'; + const tokenASymbol: string = 'EURA'; + const tokenBSymbol: string = 'stEUR'; + const tokenAAmount: string = '990000000000000000'; + const tokenBAmount: string = '990000000000000000'; + const nativeTokenAmount = '990000000000000000'; + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }), + ); + + networksUSD.forEach(network => + describe(`${network} - USD`, () => { + const dexKey = 'AngleStakedStableUSD'; + const tokenASymbol: string = 'USDA'; + const tokenBSymbol: string = 'stUSD'; + const tokenAAmount: string = '990000000000000000'; + const tokenBAmount: string = '990000000000000000'; + const nativeTokenAmount = '990000000000000000'; + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }), + ); +}); diff --git a/src/dex/angle-staked-stable/angle-staked-stable-events.test.ts b/src/dex/angle-staked-stable/angle-staked-stable-events.test.ts new file mode 100644 index 000000000..887848cb4 --- /dev/null +++ b/src/dex/angle-staked-stable/angle-staked-stable-events.test.ts @@ -0,0 +1,95 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { AngleStakedStableEventPool } from './angle-staked-stable-pool'; +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { PoolState } from './types'; +import { AngleStakedStableConfig } from './config'; + +jest.setTimeout(50 * 1000); + +async function fetchPoolState( + angleStakedStablePools: AngleStakedStableEventPool, + blockNumber: number, + poolAddress: string, +): Promise { + return angleStakedStablePools.generateState(blockNumber); +} + +// eventName -> blockNumbers +type EventMappings = Record; + +describe('AngleStakedStable EventPool Mainnet', () => { + const dexKey = 'AngleStakedStableEUR'; + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + let angleStakedStablePool: AngleStakedStableEventPool; + + // poolAddress -> EventMappings + const eventsToTest: Record = { + '0x004626a008b1acdc4c74ab51644093b155e59a23': { + Accrued: [ + 19368181, 19387091, 19387136, 19390671, 19395050, 19395068, 19404404, + 19404492, 19407830, 19410813, 19482839, 19575712, 19622932, 19622960, + 19632489, 19650535, 19668024, 19668335, 19669259, 19672358, 19675559, + 19679409, + ], + Deposit: [ + 19387091, 19387136, 19395050, 19404404, 19410813, 19622932, 19622960, + 19632489, 19668335, 19669259, 19672358, 19675559, + ], + Withdraw: [ + 19368181, 19390671, 19395068, 19404492, 19407830, 19482839, 19575712, + 19650535, 19679409, + ], + ToggledPause: [], + RateUpdated: [19668024], + }, + }; + + beforeEach(async () => { + angleStakedStablePool = new AngleStakedStableEventPool( + dexKey, + network, + dexHelper, + AngleStakedStableConfig[dexKey][network].stakeToken, + AngleStakedStableConfig[dexKey][network].agToken, + logger, + ); + }); + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async () => { + await testEventSubscriber( + angleStakedStablePool, + angleStakedStablePool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolState( + angleStakedStablePool, + _blockNumber, + poolAddress, + ), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); +}); diff --git a/src/dex/angle-staked-stable/angle-staked-stable-integration.test.ts b/src/dex/angle-staked-stable/angle-staked-stable-integration.test.ts new file mode 100644 index 000000000..83c9c3a11 --- /dev/null +++ b/src/dex/angle-staked-stable/angle-staked-stable-integration.test.ts @@ -0,0 +1,476 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface, Result } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { AngleStakedStable } from './angle-staked-stable'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { AngleStakedStableEventPool } from './angle-staked-stable-pool'; + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + amounts: bigint[], + funcName: string, +) { + return amounts.map(amount => ({ + target: exchangeAddress, + callData: readerIface.encodeFunctionData(funcName, [amount]), + })); +} + +function decodeReaderResult( + results: Result, + readerIface: Interface, + funcName: string, +) { + return results.map(result => { + const parsed = readerIface.decodeFunctionResult(funcName, result); + return BigInt(parsed[0]._hex); + }); +} + +async function checkOnChainPricing( + angleStakedStable: AngleStakedStable, + funcName: string, + blockNumber: number, + exchangeAddress: string, + prices: bigint[], + amounts: bigint[], +) { + // Normally you can get it from angleStakedStable.Iface or from eventPool. + // It depends on your implementation + const readerIface = AngleStakedStableEventPool.angleStakedStableIface; + + const readerCallData = getReaderCalldata( + exchangeAddress, + readerIface, + amounts.slice(1), + funcName, + ); + const readerResult = ( + await angleStakedStable.dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + + const expectedPrices = [0n].concat( + decodeReaderResult(readerResult, readerIface, funcName), + ); + + // No exact computation because of the bigInt approx + for (let i = 0; i < expectedPrices.length; ++i) { + expect(prices[i]).toBeGreaterThanOrEqual( + (expectedPrices[i] * 99999n) / 100000n, + ); + expect(prices[i]).toBeLessThanOrEqual( + (expectedPrices[i] * 100001n) / 100000n, + ); + } +} + +async function testPricingOnNetwork( + angleStakedStable: AngleStakedStable, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + const pools = await angleStakedStable.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await angleStakedStable.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (angleStakedStable.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + const exchange = + srcTokenSymbol === 'stEUR' || srcTokenSymbol === 'stUSD' + ? networkTokens[srcTokenSymbol].address + : networkTokens[destTokenSymbol].address; + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + angleStakedStable, + funcNameToCheck, + blockNumber, + exchange, + poolPrices![0].prices, + amounts, + ); +} + +describe('AngleStakedStable', () => { + describe('Mainnet USD', () => { + let blockNumber: number; + let angleStakedStable: AngleStakedStable; + const dexKey = 'AngleStakedStableUSD'; + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const srcTokenSymbolUSDEnter = 'USDA'; + const destTokenSymbolUSDEnter = 'stUSD'; + const funcNameSellEnter = 'previewDeposit'; + const funcNameBuyEnter = 'previewMint'; + + const destTokenSymbolUSDExit = 'USDA'; + const srcTokenSymbolUSDExit = 'stUSD'; + const funcNameSellExit = 'previewRedeem'; + const funcNameBuyExit = 'previewWithdraw'; + + const exchangeSTUSD = dexKey; + // const exchangeSTUSD = `${dexKey}_${tokens.stUSD.address.toLowerCase()}`; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 2n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 3n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 4n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 5n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 6n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 7n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 8n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 9n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + 10n * BI_POWS[tokens[srcTokenSymbolUSDEnter].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 2n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 3n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 4n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 5n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 6n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 7n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 8n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 9n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + 10n * BI_POWS[tokens[destTokenSymbolUSDEnter].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + angleStakedStable = new AngleStakedStable(network, dexKey, dexHelper); + if (angleStakedStable.initializePricing) { + await angleStakedStable.initializePricing(blockNumber); + } + }); + it('getPoolIdentifiers and getPricesVolume SELL - USDA', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolUSDEnter, + destTokenSymbolUSDEnter, + SwapSide.SELL, + amountsForSell, + funcNameSellEnter, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY - stUSD', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolUSDEnter, + destTokenSymbolUSDEnter, + SwapSide.BUY, + amountsForBuy, + funcNameBuyEnter, + ); + }); + + it('getPoolIdentifiers and getPricesVolume SELL - stUSD', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolUSDExit, + destTokenSymbolUSDExit, + SwapSide.SELL, + amountsForSell, + funcNameSellExit, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY - USDA', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolUSDExit, + destTokenSymbolUSDExit, + SwapSide.BUY, + amountsForBuy, + funcNameBuyExit, + ); + }); + + it('getTopPoolsForToken - USDA', async () => { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAngleStakedStable = new AngleStakedStable( + network, + dexKey, + dexHelper, + ); + if (newAngleStakedStable.updatePoolState) { + await newAngleStakedStable.updatePoolState(); + } + const poolLiquidity = await newAngleStakedStable.getTopPoolsForToken( + tokens[srcTokenSymbolUSDEnter].address, + 10, + ); + console.log(`${srcTokenSymbolUSDEnter} Top Pools:`, poolLiquidity); + + if (!newAngleStakedStable.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbolUSDEnter].address, + exchangeSTUSD, + ); + } + }); + + it('getTopPoolsForToken - stUSD', async () => { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAngleStakedStable = new AngleStakedStable( + network, + dexKey, + dexHelper, + ); + if (newAngleStakedStable.updatePoolState) { + await newAngleStakedStable.updatePoolState(); + } + const poolLiquidity = await newAngleStakedStable.getTopPoolsForToken( + tokens[srcTokenSymbolUSDExit].address, + 10, + ); + console.log(`${srcTokenSymbolUSDExit} Top Pools:`, poolLiquidity); + + if (!newAngleStakedStable.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbolUSDExit].address, + exchangeSTUSD, + ); + } + }); + }); + + describe('Mainnet EUR', () => { + let blockNumber: number; + let angleStakedStable: AngleStakedStable; + const dexKey = 'AngleStakedStableEUR'; + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + // Don't forget to update relevant tokens in constant-e2e.ts + const srcTokenSymbolEUREnter = 'EURA'; + const destTokenSymbolEUREnter = 'stEUR'; + const funcNameSellEnter = 'previewDeposit'; + const funcNameBuyEnter = 'previewMint'; + + const srcTokenSymbolEURExit = 'stEUR'; + const destTokenSymbolEURExit = 'EURA'; + const funcNameSellExit = 'previewRedeem'; + const funcNameBuyExit = 'previewWithdraw'; + + // const exchangeSTEUR = `${dexKey}_${tokens.stEUR.address.toLowerCase()}`; + const exchangeSTEUR = dexKey; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 2n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 3n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 4n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 5n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 6n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 7n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 8n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 9n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + 10n * BI_POWS[tokens[srcTokenSymbolEUREnter].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 2n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 3n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 4n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 5n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 6n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 7n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 8n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 9n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + 10n * BI_POWS[tokens[destTokenSymbolEUREnter].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + angleStakedStable = new AngleStakedStable(network, dexKey, dexHelper); + if (angleStakedStable.initializePricing) { + await angleStakedStable.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL - EURA', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolEUREnter, + destTokenSymbolEUREnter, + SwapSide.SELL, + amountsForSell, + funcNameSellEnter, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY - stEUR', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolEUREnter, + destTokenSymbolEUREnter, + SwapSide.BUY, + amountsForBuy, + funcNameBuyEnter, + ); + }); + + it('getPoolIdentifiers and getPricesVolume SELL - stEUR', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolEURExit, + destTokenSymbolEURExit, + SwapSide.SELL, + amountsForSell, + funcNameSellExit, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY - EURA', async () => { + await testPricingOnNetwork( + angleStakedStable, + network, + dexKey, + blockNumber, + srcTokenSymbolEURExit, + destTokenSymbolEURExit, + SwapSide.BUY, + amountsForBuy, + funcNameBuyExit, + ); + }); + + it('getTopPoolsForToken -EURA', async () => { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAngleStakedStable = new AngleStakedStable( + network, + dexKey, + dexHelper, + ); + if (newAngleStakedStable.updatePoolState) { + await newAngleStakedStable.updatePoolState(); + } + const poolLiquidity = await newAngleStakedStable.getTopPoolsForToken( + tokens[srcTokenSymbolEUREnter].address, + 10, + ); + console.log(`${srcTokenSymbolEUREnter} Top Pools:`, poolLiquidity); + + if (!newAngleStakedStable.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbolEUREnter].address, + exchangeSTEUR, + ); + } + }); + + it('getTopPoolsForToken - stEUR', async () => { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newAngleStakedStable = new AngleStakedStable( + network, + dexKey, + dexHelper, + ); + if (newAngleStakedStable.updatePoolState) { + await newAngleStakedStable.updatePoolState(); + } + const poolLiquidity = await newAngleStakedStable.getTopPoolsForToken( + tokens[srcTokenSymbolEURExit].address, + 10, + ); + console.log(`${srcTokenSymbolEURExit} Top Pools:`, poolLiquidity); + + if (!newAngleStakedStable.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbolEURExit].address, + exchangeSTEUR, + ); + } + }); + }); +}); diff --git a/src/dex/angle-staked-stable/angle-staked-stable-pool.ts b/src/dex/angle-staked-stable/angle-staked-stable-pool.ts new file mode 100644 index 000000000..b3905d81b --- /dev/null +++ b/src/dex/angle-staked-stable/angle-staked-stable-pool.ts @@ -0,0 +1,325 @@ +import { Interface } from '@ethersproject/abi'; +import type { DeepReadonly } from 'ts-essentials'; +import type { BlockHeader, Log, Logger } from '../../types'; +import { + bigIntify, + catchParseLogError, + currentBigIntTimestampInS, +} from '../../utils'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import type { IDexHelper } from '../../dex-helper/idex-helper'; +import type { PoolState } from './types'; +import StakedStableABI from '../../abi/angle/stagToken.json'; +import ERC20ABI from '../../abi/erc20.json'; + +export class AngleStakedStableEventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ) => DeepReadonly | null; + } = {}; + + static angleStakedStableIface = new Interface(StakedStableABI); + static erc20Iface = new Interface(ERC20ABI); + + logDecoder: (log: Log) => any; + + addressesSubscribed: string[]; + + BASE_27 = BigInt(1e27); + HALF_BASE_27 = BigInt(1e27 / 2); + ZERO = BigInt(0); + + constructor( + readonly parentName: string, + protected network: number, + protected dexHelper: IDexHelper, + public stakeToken: string, + public agToken: string, + logger: Logger, + ) { + super( + parentName, + `${parentName}_${stakeToken.toLowerCase()}`, + dexHelper, + logger, + ); + + this.logDecoder = (log: Log) => + AngleStakedStableEventPool.angleStakedStableIface.parseLog(log); + this.addressesSubscribed = [stakeToken]; + + // Add handlers + this.handlers.Accrued = this.handleAccrued.bind(this); + this.handlers.Deposit = this.handleDeposit.bind(this); + this.handlers.Withdraw = this.handleWithdraw.bind(this); + this.handlers.ToggledPause = this.handleToggledPause.bind(this); + this.handlers.RateUpdated = this.handleRateUpdated.bind(this); + } + + /** + * The function is called every time any of the subscribed + * addresses release log. The function accepts the current + * state, updates the state according to the log, and returns + * the updated state. + * @param state - Current state of event subscriber + * @param log - Log released by one of the subscribed addresses + * @returns Updates state of the event subscriber after the log + */ + protected processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, log, blockHeader); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * @param blockNumber - Blocknumber for which the state should + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState(blockNumber: number): Promise> { + const poolState = { + totalAssets: 0n, + totalSupply: 0n, + lastUpdate: 0n, + paused: false, + rate: 0n, + } as PoolState; + + const multicall = [ + { + target: this.agToken, + callData: AngleStakedStableEventPool.erc20Iface.encodeFunctionData( + 'balanceOf', + [this.stakeToken], + ), + }, + { + target: this.stakeToken, + callData: + AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + 'totalSupply', + ), + }, + { + target: this.stakeToken, + callData: + AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + 'lastUpdate', + ), + }, + { + target: this.stakeToken, + callData: + AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + 'paused', + ), + }, + { + target: this.stakeToken, + callData: + AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + 'rate', + ), + }, + ]; + + // on chain call + const returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(multicall) + .call({}, blockNumber) + ).returnData; + + // Decode + poolState.totalAssets = bigIntify( + AngleStakedStableEventPool.erc20Iface.decodeFunctionResult( + 'balanceOf', + returnData[0], + )[0], + ); + poolState.totalSupply = bigIntify( + AngleStakedStableEventPool.angleStakedStableIface.decodeFunctionResult( + 'totalSupply', + returnData[1], + )[0], + ); + poolState.lastUpdate = bigIntify( + AngleStakedStableEventPool.angleStakedStableIface.decodeFunctionResult( + 'lastUpdate', + returnData[2], + )[0], + ); + poolState.paused = + AngleStakedStableEventPool.angleStakedStableIface.decodeFunctionResult( + 'paused', + returnData[3], + )[0] as boolean; + + poolState.rate = bigIntify( + AngleStakedStableEventPool.angleStakedStableIface.decodeFunctionResult( + 'rate', + returnData[4], + )[0], + ); + + return poolState; + } + + async getOrGenerateState( + blockNumber: number, + ): Promise | null> { + const state = this.getState(blockNumber); + if (state) { + return state; + } + + this.logger.debug( + `No state found for ${this.addressesSubscribed[0]}, generating new one`, + ); + const newState = await this.generateState(blockNumber); + + if (!newState) { + this.logger.debug( + `Could not regenerate state for ${this.addressesSubscribed[0]}`, + ); + return null; + } + this.setState(newState, blockNumber); + return newState; + } + + getRateDeposit(amount: bigint, state: PoolState): bigint { + const newTotalAssets = this._accrue(state); + return amount === 0n || state.totalSupply === 0n + ? amount + : (amount * state.totalSupply) / newTotalAssets; + } + + getRateMint(shares: bigint, state: PoolState): bigint { + const newTotalAssets = this._accrue(state); + const roundUp = + (shares * newTotalAssets) % state.totalSupply > 0n ? 1n : 0n; + return state.totalSupply === 0n + ? shares + : (shares * newTotalAssets) / state.totalSupply + roundUp; + } + + getRateRedeem(shares: bigint, state: PoolState): bigint { + const newTotalAssets = this._accrue(state); + return state.totalSupply === 0n + ? shares + : (shares * newTotalAssets) / state.totalSupply; + } + + getRateWithdraw(amount: bigint, state: PoolState): bigint { + const newTotalAssets = this._accrue(state); + const roundUp = + (amount * state.totalSupply) % newTotalAssets > 0n ? 1n : 0n; + return amount === 0n || state.totalSupply === 0n + ? amount + : (amount * state.totalSupply) / newTotalAssets + roundUp; + } + + _accrue(state: PoolState): bigint { + const newTotalAssets = this._computeUpdatedAssets( + state.totalAssets, + state.rate, + currentBigIntTimestampInS() - state.lastUpdate, + ); + return newTotalAssets; + } + + _computeUpdatedAssets(amount: bigint, rate: bigint, exp: bigint): bigint { + if (exp === 0n || rate === 0n) return amount; + const expMinusOne = exp - 1n; + const expMinusTwo = exp > 2n ? exp - 2n : 0n; + const basePowerTwo = (rate * rate + this.HALF_BASE_27) / this.BASE_27; + const basePowerThree = + (basePowerTwo * rate + this.HALF_BASE_27) / this.BASE_27; + const secondTerm = (exp * expMinusOne * basePowerTwo) / 2n; + const thirdTerm = (exp * expMinusOne * expMinusTwo * basePowerThree) / 6n; + return ( + (amount * (this.BASE_27 + rate * exp + secondTerm + thirdTerm)) / + this.BASE_27 + ); + } + + handleAccrued( + event: any, + state: PoolState, + log: Readonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const interest = bigIntify(event.args.interest); + state.totalAssets += interest; + state.lastUpdate = bigIntify(blockHeader.timestamp); + return state; + } + + handleRateUpdated( + event: any, + state: PoolState, + log: Readonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + state.rate = bigIntify(event.args.newRate); + return state; + } + + handleDeposit( + event: any, + state: PoolState, + log: Readonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const assets = bigIntify(event.args.assets); + const shares = bigIntify(event.args.shares); + state.totalAssets += assets; + state.totalSupply += shares; + state.lastUpdate = bigIntify(blockHeader.timestamp); + return state; + } + + handleWithdraw( + event: any, + state: PoolState, + log: Readonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const assets = bigIntify(event.args.assets); + const shares = bigIntify(event.args.shares); + state.totalAssets -= assets; + state.totalSupply -= shares; + state.lastUpdate = bigIntify(blockHeader.timestamp); + return state; + } + + handleToggledPause( + event: any, + state: PoolState, + log: Readonly, + ): DeepReadonly | null { + const pauseStatus: boolean = event.args.pauseStatus; + state.paused = pauseStatus; + return state; + } +} diff --git a/src/dex/angle-staked-stable/angle-staked-stable.ts b/src/dex/angle-staked-stable/angle-staked-stable.ts new file mode 100644 index 000000000..c40a51aaf --- /dev/null +++ b/src/dex/angle-staked-stable/angle-staked-stable.ts @@ -0,0 +1,312 @@ +import { Interface } from '@ethersproject/abi'; +import type { AsyncOrSync } from 'ts-essentials'; +import type { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + SimpleExchangeParam, + PoolLiquidity, + Logger, +} from '../../types'; +import { SwapSide, type Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork } from '../../utils'; +import type { IDex } from '../../dex/idex'; +import type { IDexHelper } from '../../dex-helper/idex-helper'; +import type { AngleStakedStableData, DexParams } from './types'; +import { SimpleExchange } from '../simple-exchange'; +import { AngleStakedStableConfig, Adapters } from './config'; +import { AngleStakedStableEventPool } from './angle-staked-stable-pool'; + +const AngleStakedGasCost = 80000; + +export class AngleStakedStable + extends SimpleExchange + implements IDex +{ + protected config: DexParams; + + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = true; + + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(AngleStakedStableConfig); + + logger: Logger; + + protected eventPools: { [key: string]: AngleStakedStableEventPool } = {}; + protected isPaused: { [key: string]: boolean } = {}; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + ) { + super(dexHelper, dexKey); + const config = AngleStakedStableConfig[dexKey][network]; + this.logger = dexHelper.getLogger(dexKey); + this.config = { + stakeToken: config.stakeToken.toLowerCase(), + agToken: config.agToken.toLowerCase(), + }; + } + + async initializePricing(blockNumber: number) { + this.eventPools[this.config.stakeToken] = new AngleStakedStableEventPool( + this.dexKey, + this.network, + this.dexHelper, + this.config.stakeToken, + this.config.agToken, + this.logger, + ); + await this.eventPools[this.config.stakeToken].initialize(blockNumber); + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + const knownInfo = this._knownAddress(srcToken, destToken); + if (!knownInfo.known) return []; + return [`${this.dexKey}_${knownInfo.stakeToken!.toLowerCase()}`]; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + const srcTokenAddress = srcToken.address.toLowerCase(); + const destTokenAddress = destToken.address.toLowerCase(); + const knownInfo = this._knownAddress(srcToken, destToken); + if (!knownInfo.known) return null; + + const agToken = knownInfo.agToken!; + const stakeToken = knownInfo.stakeToken!; + const eventPool = this.eventPools[stakeToken]; + const exchange = `${this.dexKey}`; + const state = await eventPool?.getOrGenerateState(blockNumber); + if (this.eventPools === null || state === undefined || state === null) + return null; + if (srcTokenAddress === agToken && side === SwapSide.SELL) + return [ + { + prices: amounts.map(amount => + eventPool.getRateDeposit(amount, state), + ), + unit: eventPool.getRateDeposit(1n * BigInt(10 ** 18), state), + gasCost: AngleStakedGasCost, + exchange: exchange, + data: { exchange: stakeToken }, + poolAddresses: [stakeToken], + }, + ]; + if (destTokenAddress === agToken && side === SwapSide.SELL) + return [ + { + prices: amounts.map(share => eventPool.getRateRedeem(share, state)), + unit: eventPool.getRateRedeem(1n * BigInt(10 ** 18), state), + gasCost: AngleStakedGasCost, + exchange: exchange, + data: { exchange: stakeToken }, + poolAddresses: [stakeToken], + }, + ]; + if (srcTokenAddress === agToken && side === SwapSide.BUY) + return [ + { + prices: amounts.map(share => eventPool.getRateMint(share, state)), + unit: eventPool.getRateMint(1n * BigInt(10 ** 18), state), + gasCost: AngleStakedGasCost, + exchange: exchange, + data: { exchange: stakeToken }, + poolAddresses: [stakeToken], + }, + ]; + return [ + { + prices: amounts.map(amount => eventPool.getRateWithdraw(amount, state)), + unit: eventPool.getRateWithdraw(1n * BigInt(10 ** 18), state), + gasCost: AngleStakedGasCost, + exchange: exchange, + data: { exchange: stakeToken }, + poolAddresses: [stakeToken], + }, + ]; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost( + poolPrices: PoolPrices, + ): number | number[] { + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: AngleStakedStableData, + side: SwapSide, + ): AdapterExchangeParam { + const { exchange } = data; + + const payload = this.abiCoder.encodeParameter( + { + ParentStruct: { + toStaked: 'bool', + }, + }, + { + toStaked: srcToken.toLowerCase() === this.config.agToken, + }, + ); + + return { + targetExchange: exchange, + payload, + networkFee: '0', + }; + } + + // Encode call data used by simpleSwap like routers + // Used for simpleSwap & simpleBuy + // Hint: this.buildSimpleParamWithoutWETHConversion + // could be useful + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: AngleStakedStableData, + side: SwapSide, + ): Promise { + const { exchange } = data; + + // Encode here the transaction arguments + const swapData = + srcToken.toLowerCase() === this.config.agToken + ? AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + side === SwapSide.SELL ? 'deposit' : 'mint', + [ + side === SwapSide.SELL ? srcAmount : destAmount, + this.augustusAddress, + ], + ) + : AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + side === SwapSide.SELL ? 'redeem' : 'withdraw', + [ + side === SwapSide.SELL ? srcAmount : destAmount, + this.augustusAddress, + this.augustusAddress, + ], + ); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + exchange, + ); + } + + // This is called once before getTopPoolsForToken is + // called for multiple tokens. This can be helpful to + // update common state required for calculating + // getTopPoolsForToken. It is optional for a DEX + // to implement this + async updatePoolState(): Promise { + const tokenBalanceMultiCall = [ + { + target: this.config.stakeToken, + callData: + AngleStakedStableEventPool.angleStakedStableIface.encodeFunctionData( + 'paused', + ), + }, + ]; + const returnData = ( + await this.dexHelper.multiContract.methods + .aggregate(tokenBalanceMultiCall) + .call() + ).returnData; + + this.isPaused[this.config.stakeToken] = + AngleStakedStableEventPool.angleStakedStableIface.decodeFunctionResult( + 'paused', + returnData[0], + )[0] as boolean; + } + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + if ( + this.isPaused[this.config.stakeToken] || + (tokenAddress.toLowerCase() !== this.config.agToken && + tokenAddress.toLowerCase() !== this.config.stakeToken) + ) { + return []; + } + + return [ + { + exchange: this.dexKey, + address: this.config.stakeToken, + connectorTokens: [ + tokenAddress.toLowerCase() === this.config.agToken + ? ({ address: this.config.stakeToken, decimals: 18 } as Token) + : ({ address: this.config.agToken, decimals: 18 } as Token), + ], + // liquidity is infinite as to have been able to mint stakeToken, you must have deposited agToken + liquidityUSD: 1e12, + }, + ]; + } + + // This is optional function in case if your implementation has acquired any resources + // you need to release for graceful shutdown. For example, it may be any interval timer + releaseResources(): AsyncOrSync {} + + _knownAddress( + srcToken: Token, + destToken: Token, + ): { known: boolean; agToken: string | null; stakeToken: string | null } { + const srcTokenAddress = srcToken.address.toLowerCase(); + const destTokenAddress = destToken.address.toLowerCase(); + if ( + (srcTokenAddress === this.config.agToken && + destTokenAddress === this.config.stakeToken) || + (srcTokenAddress === this.config.stakeToken && + destTokenAddress === this.config.agToken) + ) { + return { + known: true, + agToken: this.config.agToken, + stakeToken: this.config.stakeToken, + }; + } + return { known: false, agToken: null, stakeToken: null }; + } +} diff --git a/src/dex/angle-staked-stable/config.ts b/src/dex/angle-staked-stable/config.ts new file mode 100644 index 000000000..61516c907 --- /dev/null +++ b/src/dex/angle-staked-stable/config.ts @@ -0,0 +1,139 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const AngleStakedStableConfig: DexConfigMap = { + // USDA <-> stUSD + AngleStakedStableUSD: { + [Network.ARBITRUM]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + [Network.MAINNET]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + [Network.OPTIMISM]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + [Network.POLYGON]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + [Network.BASE]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + [Network.BSC]: { + agToken: '0x0000206329b97db379d5e1bf586bbdb969c63274', + stakeToken: '0x0022228a2cc5e7ef0274a7baa600d44da5ab5776', + }, + }, + // EURA <-> stEUR + AngleStakedStableEUR: { + [Network.ARBITRUM]: { + agToken: '0xfa5ed56a203466cbbc2430a43c66b9d8723528e7', + stakeToken: '0x004626a008b1acdc4c74ab51644093b155e59a23', + }, + [Network.MAINNET]: { + agToken: '0x1a7e4e63778b4f12a199c062f3efdd288afcbce8', + stakeToken: '0x004626a008b1acdc4c74ab51644093b155e59a23', + }, + [Network.OPTIMISM]: { + agToken: '0x9485aca5bbbe1667ad97c7fe7c4531a624c8b1ed', + stakeToken: '0x004626a008b1acdc4c74ab51644093b155e59a23', + }, + [Network.POLYGON]: { + agToken: '0xe0b52e49357fd4daf2c15e02058dce6bc0057db4', + stakeToken: '0x004626a008b1acdc4c74ab51644093b155e59a23', + }, + }, +}; + +export const Adapters: Record = { + [Network.ARBITRUM]: { + [SwapSide.SELL]: [ + { + name: 'ArbitrumAdapter03', + index: 1, + }, + ], + [SwapSide.BUY]: [ + { + name: 'ArbitrumBuyAdapter', + index: 10, + }, + ], + }, + [Network.MAINNET]: { + [SwapSide.SELL]: [ + { + name: 'Adapter06', + index: 1, + }, + ], + [SwapSide.BUY]: [ + { + name: 'BuyAdapter02', + index: 4, + }, + ], + }, + [Network.OPTIMISM]: { + [SwapSide.SELL]: [ + { + name: 'OptimismAdapter02', + index: 1, + }, + ], + [SwapSide.BUY]: [ + { + name: 'OptimismBuyAdapter', + index: 6, + }, + ], + }, + [Network.BASE]: { + [SwapSide.SELL]: [ + { + name: 'BaseAdapter01', + index: 11, + }, + ], + [SwapSide.BUY]: [ + { + name: 'BaseBuyAdapter', + index: 7, + }, + ], + }, + [Network.POLYGON]: { + [SwapSide.SELL]: [ + { + name: 'PolygonAdapter02', + index: 10, + }, + ], + [SwapSide.BUY]: [ + { + name: 'PolygonBuyAdapter', + index: 9, + }, + ], + }, + [Network.BSC]: { + [SwapSide.SELL]: [ + { + name: 'BscAdapter02', + index: 10, + }, + ], + [SwapSide.BUY]: [ + { + name: 'BscBuyAdapter', + index: 8, + }, + ], + }, +}; diff --git a/src/dex/angle-staked-stable/types.ts b/src/dex/angle-staked-stable/types.ts new file mode 100644 index 000000000..e1dea52b8 --- /dev/null +++ b/src/dex/angle-staked-stable/types.ts @@ -0,0 +1,16 @@ +import { Address } from '../../types'; + +export type PoolState = { + totalAssets: bigint; + totalSupply: bigint; + lastUpdate: bigint; + rate: bigint; + paused: boolean; +}; + +export type AngleStakedStableData = { exchange: Address }; + +export type DexParams = { + agToken: Address; + stakeToken: Address; +}; diff --git a/src/dex/index.ts b/src/dex/index.ts index 929e93544..180658419 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -79,6 +79,7 @@ import { SpiritSwapV3 } from './quickswap/spiritswap-v3'; import { TraderJoeV21 } from './trader-joe-v2.1'; import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3'; import { Algebra } from './algebra/algebra'; +import { AngleStakedStable } from './angle-staked-stable/angle-staked-stable'; import { QuickPerps } from './quick-perps/quick-perps'; import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; import { Dexalot } from './dexalot/dexalot'; @@ -164,6 +165,7 @@ const Dexes = [ MaverickV1, Camelot, SwaapV2, + AngleStakedStable, QuickPerps, NomiswapV2, SolidlyV3, diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 0dd68f5d1..dff1a602d 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -323,6 +323,22 @@ export const Tokens: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3', decimals: 6, }, + EURA: { + address: '0x1a7e4e63778b4f12a199c062f3efdd288afcbce8', + decimals: 18, + }, + stEUR: { + address: '0x004626a008b1acdc4c74ab51644093b155e59a23', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, GHO: { address: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', decimals: 18, @@ -489,6 +505,22 @@ export const Tokens: { address: '0xa3fa99a148fa48d14ed51d610c367c61876997f1', decimals: 18, }, + EURA: { + address: '0xe0b52e49357fd4daf2c15e02058dce6bc0057db4', + decimals: 18, + }, + stEUR: { + address: '0x004626a008b1acdc4c74ab51644093b155e59a23', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, BUSD: { address: '0x9C9e5fD8bbc25984B178FdCE6117Defa39d2db39', decimals: 18, @@ -687,6 +719,18 @@ export const Tokens: { address: '0xFdc66A08B0d0Dc44c17bbd471B88f49F50CdD20F', decimals: 18, }, + EURA: { + address: '0x12f31B73D812C6Bb0d735a218c086d44D5fe5f89', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, }, [Network.AVALANCHE]: { PHAR: { @@ -937,6 +981,22 @@ export const Tokens: { address: '0x6bB7A17AcC227fd1F6781D1EEDEAE01B42047eE0', decimals: 18, }, + EURA: { + address: '0xfa5ed56a203466cbbc2430a43c66b9d8723528e7', + decimals: 18, + }, + stEUR: { + address: '0x004626a008b1acdc4c74ab51644093b155e59a23', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, GRAIL: { address: '0x3d9907f9a368ad0a51be60f7da3b97cf940982d8', decimals: 18, @@ -1008,6 +1068,22 @@ export const Tokens: { address: '0x68f180fcCe6836688e9084f035309E29Bf0A2095', decimals: 8, }, + EURA: { + address: '0x9485aca5bbbe1667ad97c7fe7c4531a624c8b1ed', + decimals: 18, + }, + stEUR: { + address: '0x004626a008b1acdc4c74ab51644093b155e59a23', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, frxETH: { address: '0x6806411765Af15Bddd26f8f544A34cC40cb9838B', decimals: 18, @@ -1076,6 +1152,18 @@ export const Tokens: { address: '0xFd4330b0312fdEEC6d4225075b82E00493FF2e3f', decimals: 18, }, + EURA: { + address: '0xA61BeB4A3d02decb01039e378237032B351125B4', + decimals: 18, + }, + USDA: { + address: '0x0000206329b97DB379d5E1Bf586BbDB969C63274', + decimals: 18, + }, + stUSD: { + address: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', + decimals: 18, + }, ETH: { address: ETHER_ADDRESS, decimals: 18 }, }, }; @@ -1128,7 +1216,7 @@ export const Holders: { EURS: '0xC1056Adeb61a01964Ea265cA95EffB7016f9Ed78', EURT: '0x6914FC70fAC4caB20a8922E900C4BA57fEECf8E1', CRV: '0x7a16fF8270133F063aAb6C9977183D9e72835428', - jEUR: '0x4f0CF2F63913524b85c1126AB7eE7957857f3482', + jEUR: '0x937Df4e3d6dB229A10ff0098ab3A1bCC40C33ea4', UST: '0xf16e9b0d03470827a95cdfd0cb8a8a3b46969b91', SAITAMA: '0x763d5d93f27615aac852b70549f5877b92193864', aETH: '0xc03c4476fbe25138bf724fa1b95551c6e6b8fd2c', @@ -1144,6 +1232,10 @@ export const Holders: { aEthWETH: '0x645C4c0c95C1Aa6EF25d12f4a25038cA9b0C6Cc7', dUSDC: '0x2FC2F705110A7F46Ce85F701d7217EF1018f01A3', PSP: '0xE5E5440a1CE69C5cf67BFFA74d185e57c31b43E5', + EURA: '0xa116f421ff82a9704428259fd8cc63347127b777', + stEUR: '0xdC7Aa225964267c7E0EfB35f4931426209E90312', + USDA: '0x2686bC6A56D205010637CE1DF124b20Cb19E4054', + stUSD: '0x4e83c0a323b68E3Bc7CC8a4E35326Fd0544A291E', crvUSD: '0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635', GHO: '0x844Dc85EdD8492A56228D293cfEbb823EF3E10EC', wibBTC: '0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B', @@ -1189,6 +1281,10 @@ export const Holders: { amUSDC: '0x6e7f19cd23049c7118e14470e2bf85d2e26ee0ae', MAI: '0x9a8cf02f3e56c664ce75e395d0e4f3dc3dafe138', SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', + EURA: '0x9A760aa1Fe631fD9aC0Aee0965736121c7c132cc', + stEUR: '0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701', + USDA: '0x741383AbD73891b40822A069f14d6fc5b5685020', + stUSD: '0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701', crvUSD: '0x9D3a22A71C2bddFEF006f1c207C06B0A5f42f95F', }, [Network.FANTOM]: { @@ -1239,6 +1335,9 @@ export const Holders: { USDFI: '0x2E00D722e091836B39Db3e4dcE6eE51c90c5B221', SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', BNBx: '0xFF4606bd3884554CDbDabd9B6e25E2faD4f6fc54', + EURA: '0x4A5362ef534FFB27510E4E4C9A215BB5436377C2', + USDA: '0x230c1f68aBE6033Cba3Fe0D2C0D7097e9923C3bC', + stUSD: '0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776', }, [Network.AVALANCHE]: { AVAX: '0xD6216fC19DB775Df9774a6E33526131dA7D19a2c', @@ -1302,6 +1401,10 @@ export const Holders: { RDNT: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', SDEX: '0xb0470cf15b22a6a32c49a7c20e3821b944a76058', wstETH: '0x916792f7734089470de27297903bed8a4630b26d', + EURA: '0x6dd7b830896b56812aa667bdd14b71c8b3252f8e', + stEUR: '0xE588611e7A2392507879E3be80531654b85C16aA', + USDA: '0xa86ff337db9107b54862d30d1a598f8be847b05e', + stUSD: '0xa9ddd91249dfdd450e81e1c56ab60e1a62651701', crvUSD: '0x171c53d55b1bcb725f660677d9e8bad7fd084282', }, [Network.OPTIMISM]: { @@ -1321,6 +1424,10 @@ export const Holders: { rETH: '0x4c2e69e58b14de9afedfb94319519ce34e087283', WBTC: '0xb9c8f0d3254007ee4b98970b94544e473cd610ec', frxETH: '0x4d4edf8291d169f975b99914b6ab3326abb45938', + EURA: '0xC18dAC166eDa9538933258d21A272C1775C19c73', + stEUR: '0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701', + USDA: '0xC18dAC166eDa9538933258d21A272C1775C19c73', + stUSD: '0xC98b0729695A25152B8D5b6B95709070605A7F60', crvUSD: '0xD1A992417a0ABFFa632Cbde4DA9F5DcF85CAa858', }, [Network.ZKEVM]: { @@ -1341,6 +1448,10 @@ export const Holders: { BAL: '0x854b004700885a61107b458f11ecc169a019b764', GOLD: '0x1374c25b3710758c326ee0c70ec48b595d5ccf8c', SDEX: '0xa5d378c05192e3f1f365d6298921879c4d51c5a3', + EURA: '0x5b5614b9fffab7c751799eb12d5cb9165c8c40ad', + stEUR: '0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701', + USDA: '0x177772af6669aca61c23d325ab4213e8ba56c79d', + stUSD: '0x8deeffb6047b8ee91b09334eb2a4ca120f43f596', ALB: '0x365c6d588e8611125de3bea5b9280c304fa54113', }, };